From abdc31a207b2ea31284c9ad8d26cad8ecd139fdf Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 12:14:19 -0500 Subject: [PATCH 01/21] chore: improve package.js description --- packages/react-meteor-accounts/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-meteor-accounts/package.js b/packages/react-meteor-accounts/package.js index 3fa1fb84..4ecd2209 100644 --- a/packages/react-meteor-accounts/package.js +++ b/packages/react-meteor-accounts/package.js @@ -2,7 +2,7 @@ Package.describe({ name: 'react-accounts', - summary: 'React hook for reactively tracking Meteor data', + summary: 'React hook for reactively tracking Meteor Accounts data', version: '1.0.0-beta.1', documentation: 'README.md', git: 'https://github.com/meteor/react-packages', From a222ece61623984ca50e253058513616cfa505ad Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 12:36:40 -0500 Subject: [PATCH 02/21] chore: add dep on 'accounts-base' --- packages/react-meteor-accounts/package.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-meteor-accounts/package.js b/packages/react-meteor-accounts/package.js index 4ecd2209..388fa7d0 100644 --- a/packages/react-meteor-accounts/package.js +++ b/packages/react-meteor-accounts/package.js @@ -10,8 +10,8 @@ Package.describe({ Package.onUse((api) => { api.versionsFrom(['1.10', '2.3']); - api.use('tracker'); - api.use('typescript'); + + api.use(['accounts-base', 'tracker', 'typescript']); api.mainModule('react-accounts.tsx', ['client', 'server'], { lazy: true }); }); From 2579d4b156f84a84aa69b1093f65237689a72e85 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 12:37:39 -0500 Subject: [PATCH 03/21] fix(types): wrong / incomplete function signatures --- packages/react-meteor-accounts/package.json | 2 +- .../react-meteor-accounts/react-accounts.tsx | 18 +++++++++--------- .../types/react-accounts.d.ts | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/react-meteor-accounts/package.json b/packages/react-meteor-accounts/package.json index 03667b32..de1eac94 100644 --- a/packages/react-meteor-accounts/package.json +++ b/packages/react-meteor-accounts/package.json @@ -1,7 +1,7 @@ { "name": "meteor-react-accounts", "scripts": { - "make-types": "npx typescript *.tsx --jsx preserve --declaration --emitDeclarationOnly --outDir types" + "make-types": "npx typescript *.tsx --jsx preserve --declaration --emitDeclarationOnly --esModuleInterop --outDir types" }, "devDependencies": { "@testing-library/react": "^10.0.2", diff --git a/packages/react-meteor-accounts/react-accounts.tsx b/packages/react-meteor-accounts/react-accounts.tsx index 95a92334..9753e8cc 100644 --- a/packages/react-meteor-accounts/react-accounts.tsx +++ b/packages/react-meteor-accounts/react-accounts.tsx @@ -1,8 +1,8 @@ import { Meteor } from 'meteor/meteor' import { Tracker } from 'meteor/tracker' -import { useState, useEffect, forwardRef } from 'react' +import React, { useState, useEffect, forwardRef } from 'react' -export const useUserId = () => { +export function useUserId(): string | null { const [userId, setUserId] = useState(Meteor.userId()) useEffect(() => { const computation = Tracker.autorun(() => { @@ -15,14 +15,14 @@ export const useUserId = () => { return userId } -export const withUserId = (Component) => ( - forwardRef((props, ref) => { +export function withUserId

(Component: React.ComponentType

) { + return forwardRef((props: P, ref) => { const userId = useUserId(); return }) -) +} -export const useUser = () => { +export function useUser(): Meteor.User | null { const [user, setUser] = useState(Meteor.user()) useEffect(() => { const computation = Tracker.autorun(() => { @@ -35,9 +35,9 @@ export const useUser = () => { return user } -export const withUser = (Component) => ( - forwardRef((props, ref) => { +export function withUser

(Component: React.ComponentType

) { + return forwardRef((props: P, ref) => { const user = useUser(); return }) -) +} diff --git a/packages/react-meteor-accounts/types/react-accounts.d.ts b/packages/react-meteor-accounts/types/react-accounts.d.ts index c66598b5..b813f3db 100644 --- a/packages/react-meteor-accounts/types/react-accounts.d.ts +++ b/packages/react-meteor-accounts/types/react-accounts.d.ts @@ -1,6 +1,6 @@ -/// import { Meteor } from 'meteor/meteor'; -export declare const useUserId: () => string; -export declare const withUserId: (Component: any) => import("react").ForwardRefExoticComponent>; -export declare const useUser: () => Meteor.User; -export declare const withUser: (Component: any) => import("react").ForwardRefExoticComponent>; +import React from 'react'; +export declare function useUserId(): string | null; +export declare function withUserId

(Component: React.ComponentType

): React.ForwardRefExoticComponent & React.RefAttributes>; +export declare function useUser(): Meteor.User | null; +export declare function withUser

(Component: React.ComponentType

): React.ForwardRefExoticComponent & React.RefAttributes>; From 4ceeffcb01e6bdc679092a27f06d5b00f5200e09 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 12:44:37 -0500 Subject: [PATCH 04/21] feat: add index file and check React version --- packages/react-meteor-accounts/index.ts | 12 ++++++++++++ packages/react-meteor-accounts/package.js | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/react-meteor-accounts/index.ts diff --git a/packages/react-meteor-accounts/index.ts b/packages/react-meteor-accounts/index.ts new file mode 100644 index 00000000..074d0fd5 --- /dev/null +++ b/packages/react-meteor-accounts/index.ts @@ -0,0 +1,12 @@ +import { Meteor } from 'meteor/meteor'; +import React from 'react'; + +if (Meteor.isDevelopment) { + // Custom check instead of `checkNpmVersions` to reduce prod bundle size (~8kb). + const v = React.version.split('.').map(val => parseInt(val)); + if (v[0] < 16 || (v[0] === 16 && v[1] < 8)) { + console.warn('react-meteor-accounts requires React version >= 16.8.'); + } +} + +export { useUser, withUser, useUserId, withUserId } from './react-accounts'; diff --git a/packages/react-meteor-accounts/package.js b/packages/react-meteor-accounts/package.js index 388fa7d0..915997ef 100644 --- a/packages/react-meteor-accounts/package.js +++ b/packages/react-meteor-accounts/package.js @@ -13,5 +13,5 @@ Package.onUse((api) => { api.use(['accounts-base', 'tracker', 'typescript']); - api.mainModule('react-accounts.tsx', ['client', 'server'], { lazy: true }); + api.mainModule('index.ts', ['client', 'server'], { lazy: true }); }); From 0bce7bed18952c6d31d46fb595d970c78ce0edfa Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 13:19:16 -0500 Subject: [PATCH 05/21] chore: add jsdoc descriptions to fns --- .../react-meteor-accounts/react-accounts.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/react-meteor-accounts/react-accounts.tsx b/packages/react-meteor-accounts/react-accounts.tsx index 9753e8cc..f5940ebb 100644 --- a/packages/react-meteor-accounts/react-accounts.tsx +++ b/packages/react-meteor-accounts/react-accounts.tsx @@ -2,6 +2,10 @@ import { Meteor } from 'meteor/meteor' import { Tracker } from 'meteor/tracker' import React, { useState, useEffect, forwardRef } from 'react' +/** + * Hook to get a stateful value of the current user id from `Meteor.userId`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-userId + */ export function useUserId(): string | null { const [userId, setUserId] = useState(Meteor.userId()) useEffect(() => { @@ -15,6 +19,10 @@ export function useUserId(): string | null { return userId } +/** + * HOC to forward a stateful value of the current user id from `Meteor.userId`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-userId + */ export function withUserId

(Component: React.ComponentType

) { return forwardRef((props: P, ref) => { const userId = useUserId(); @@ -22,6 +30,10 @@ export function withUserId

(Component: React.ComponentType

) { }) } +/** + * Hook to get a stateful value of the current user record from `Meteor.user`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-user + */ export function useUser(): Meteor.User | null { const [user, setUser] = useState(Meteor.user()) useEffect(() => { @@ -35,6 +47,10 @@ export function useUser(): Meteor.User | null { return user } +/** + * HOC to get a stateful value of the current user record from `Meteor.user`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-user + */ export function withUser

(Component: React.ComponentType

) { return forwardRef((props: P, ref) => { const user = useUser(); From b1f572179893aa3dd938bfc4a140fdccc89b34a7 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 13:23:53 -0500 Subject: [PATCH 06/21] chore(docs): adjust return types and ts signatures --- packages/react-meteor-accounts/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-meteor-accounts/README.md b/packages/react-meteor-accounts/README.md index ff6eab3f..1e1e1ca9 100644 --- a/packages/react-meteor-accounts/README.md +++ b/packages/react-meteor-accounts/README.md @@ -93,10 +93,10 @@ TypeScript signatures: ```ts // Hook -const useUser: () => Meteor.User; +function useUser(): Meteor.User | null; // HOC -const withUser: (Component: any) => React.ForwardRefExoticComponent>; +function withUser

(Component: React.ComponentType

): React.ForwardRefExoticComponent & React.RefAttributes>; ``` ### useUserId() / withUserId(...) @@ -106,7 +106,7 @@ Get a stateful value of the current user id from [`Meteor.userId`](https://docs. The hook, `useUserId()`, returns a stateful value of the current user id. - Arguments: *none*. -- Returns: `string`. +- Returns: `string | null`. The HOC, `withUserId(Component)`, returns a wrapped version of `Component`, where `Component` receives a prop of the current user id, `userId`. @@ -114,7 +114,7 @@ The HOC, `withUserId(Component)`, returns a wrapped version of `Component`, wher | Argument | Type | Required | Description | | --- | --- | --- | --- | -| Component | `any` | yes | A React component. | +| Component | `React.ComponentType` | yes | A React component. | - Returns: `React.ForwardRefExoticComponent`. @@ -163,8 +163,8 @@ TypeScript signatures: ```ts // Hook -const useUserId: () => string; +function useUserId(): string | null; // HOC -const withUserId: (Component: any) => React.ForwardRefExoticComponent>; +function withUserId

(Component: React.ComponentType

): React.ForwardRefExoticComponent & React.RefAttributes>; ``` From 5f05cccf84797e3b68ce6f850e71fbb7ae1c73fd Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 13:31:45 -0500 Subject: [PATCH 07/21] feat: let forwarded props be overridable --- packages/react-meteor-accounts/react-accounts.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-meteor-accounts/react-accounts.tsx b/packages/react-meteor-accounts/react-accounts.tsx index f5940ebb..886460b1 100644 --- a/packages/react-meteor-accounts/react-accounts.tsx +++ b/packages/react-meteor-accounts/react-accounts.tsx @@ -26,7 +26,7 @@ export function useUserId(): string | null { export function withUserId

(Component: React.ComponentType

) { return forwardRef((props: P, ref) => { const userId = useUserId(); - return + return }) } @@ -54,6 +54,6 @@ export function useUser(): Meteor.User | null { export function withUser

(Component: React.ComponentType

) { return forwardRef((props: P, ref) => { const user = useUser(); - return + return }) } From 5e86dd2f4cf60d693896ef52de08cd85936ac7ee Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 13:36:27 -0500 Subject: [PATCH 08/21] fix: hoc type signatures not injecting prop --- packages/react-meteor-accounts/react-accounts.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-meteor-accounts/react-accounts.tsx b/packages/react-meteor-accounts/react-accounts.tsx index 886460b1..1dc1da3e 100644 --- a/packages/react-meteor-accounts/react-accounts.tsx +++ b/packages/react-meteor-accounts/react-accounts.tsx @@ -23,7 +23,9 @@ export function useUserId(): string | null { * HOC to forward a stateful value of the current user id from `Meteor.userId`, a reactive data source. * @see https://docs.meteor.com/api/accounts.html#Meteor-userId */ -export function withUserId

(Component: React.ComponentType

) { +export function withUserId

(Component: React.ComponentType

) { return forwardRef((props: P, ref) => { const userId = useUserId(); return @@ -51,7 +53,9 @@ export function useUser(): Meteor.User | null { * HOC to get a stateful value of the current user record from `Meteor.user`, a reactive data source. * @see https://docs.meteor.com/api/accounts.html#Meteor-user */ -export function withUser

(Component: React.ComponentType

) { +export function withUser

(Component: React.ComponentType

) { return forwardRef((props: P, ref) => { const user = useUser(); return From 9d8a7c21a02eba1cd453683bec51078e745ebb14 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 13:57:04 -0500 Subject: [PATCH 09/21] feat: add utils for loggingIn and loggingOut --- .../react-meteor-accounts/react-accounts.tsx | 73 ++++++++++++++++++- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/packages/react-meteor-accounts/react-accounts.tsx b/packages/react-meteor-accounts/react-accounts.tsx index 1dc1da3e..9c4add63 100644 --- a/packages/react-meteor-accounts/react-accounts.tsx +++ b/packages/react-meteor-accounts/react-accounts.tsx @@ -2,8 +2,15 @@ import { Meteor } from 'meteor/meteor' import { Tracker } from 'meteor/tracker' import React, { useState, useEffect, forwardRef } from 'react' +// Augmentation to add missing signature +declare module 'meteor/meteor' { + module Meteor { + function loggingOut(): boolean; + } +} + /** - * Hook to get a stateful value of the current user id from `Meteor.userId`, a reactive data source. + * Hook to get a stateful value of the current user id. Uses `Meteor.userId`, a reactive data source. * @see https://docs.meteor.com/api/accounts.html#Meteor-userId */ export function useUserId(): string | null { @@ -20,7 +27,7 @@ export function useUserId(): string | null { } /** - * HOC to forward a stateful value of the current user id from `Meteor.userId`, a reactive data source. + * HOC to forward a stateful value of the current user id. Uses `Meteor.userId`, a reactive data source. * @see https://docs.meteor.com/api/accounts.html#Meteor-userId */ export function withUserId

}) } + +/** + * Hook to get a stateful value of whether a login method (e.g. `loginWith`) is currently in progress. Uses `Meteor.loggingIn`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-loggingIn + */ +export function useLoggingIn(): boolean { + const [loggingIn, setLoggingIn] = useState(Meteor.loggingIn()) + useEffect(() => { + const computation = Tracker.autorun(() => { + setLoggingIn(Meteor.loggingIn()) + }) + return () => { + computation.stop() + } + }, []) + return loggingIn +} + +/** + * HOC to forward a stateful value of whether a login method (e.g. `loginWith`) is currently in progress. Uses `Meteor.loggingIn`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-loggingIn + */ +export function withLoggingIn

(Component: React.ComponentType

) { + return forwardRef((props: P, ref) => { + const loggingIn = useLoggingIn(); + return + }) +} + +/** + * Hook to get a stateful value of whether the logout method is currently in progress. Uses `Meteor.loggingOut`, a reactive data source. + */ +export function useLoggingOut(): boolean { + const [loggingOut, setLoggingOut] = useState(Meteor.loggingOut()) + useEffect(() => { + const computation = Tracker.autorun(() => { + setLoggingOut(Meteor.loggingOut()) + }) + return () => { + computation.stop() + } + }, []) + return loggingOut +} + +/** + * HOC to forward a stateful value of whether the logout method is currently in progress. Uses `Meteor.loggingOut`, a reactive data source. + */ +export function withLoggingOut

(Component: React.ComponentType

) { + return forwardRef((props: P, ref) => { + const loggingOut = useLoggingOut(); + return + }) +} From 16f35624c52a1e4e2c2e6cc55ab4cf05e9380100 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 15:42:34 -0500 Subject: [PATCH 10/21] test(useUserId): initial tests --- packages/react-meteor-accounts/index.tests.ts | 1 + .../react-meteor-accounts/package-lock.json | 62 +++++++++ packages/react-meteor-accounts/package.js | 13 ++ packages/react-meteor-accounts/package.json | 1 + .../react-accounts.tests.tsx | 122 ++++++++++++++++++ 5 files changed, 199 insertions(+) create mode 100644 packages/react-meteor-accounts/index.tests.ts create mode 100644 packages/react-meteor-accounts/react-accounts.tests.tsx diff --git a/packages/react-meteor-accounts/index.tests.ts b/packages/react-meteor-accounts/index.tests.ts new file mode 100644 index 00000000..628be844 --- /dev/null +++ b/packages/react-meteor-accounts/index.tests.ts @@ -0,0 +1 @@ +import './react-accounts.tests.tsx'; diff --git a/packages/react-meteor-accounts/package-lock.json b/packages/react-meteor-accounts/package-lock.json index b4ae92e5..abdc6559 100644 --- a/packages/react-meteor-accounts/package-lock.json +++ b/packages/react-meteor-accounts/package-lock.json @@ -100,6 +100,30 @@ "@testing-library/dom": "^7.22.3" } }, + "@testing-library/react-hooks": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz", + "integrity": "sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@types/react": ">=16.9.0", + "@types/react-dom": ">=16.9.0", + "@types/react-test-renderer": ">=16.9.0", + "react-error-boundary": "^3.1.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz", + "integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, "@types/aria-query": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", @@ -192,6 +216,24 @@ "csstype": "^3.0.2" } }, + "@types/react-dom": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", + "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-test-renderer": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz", + "integrity": "sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/underscore": { "version": "1.10.24", "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.24.tgz", @@ -433,6 +475,26 @@ "scheduler": "^0.19.1" } }, + "react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.16.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz", + "integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/packages/react-meteor-accounts/package.js b/packages/react-meteor-accounts/package.js index 915997ef..ec90f6be 100644 --- a/packages/react-meteor-accounts/package.js +++ b/packages/react-meteor-accounts/package.js @@ -15,3 +15,16 @@ Package.onUse((api) => { api.mainModule('index.ts', ['client', 'server'], { lazy: true }); }); + +Package.onTest((api) => { + api.use([ + 'accounts-base', + 'accounts-password', + 'react-accounts', + 'tinytest', + 'tracker', + 'typescript', + ]); + + api.mainModule('index.tests.ts'); +}); diff --git a/packages/react-meteor-accounts/package.json b/packages/react-meteor-accounts/package.json index de1eac94..8dc3e3e6 100644 --- a/packages/react-meteor-accounts/package.json +++ b/packages/react-meteor-accounts/package.json @@ -5,6 +5,7 @@ }, "devDependencies": { "@testing-library/react": "^10.0.2", + "@testing-library/react-hooks": "^7.0.2", "@types/meteor": "^1.4.42", "@types/react": "^16.9.34", "react": "16.13.1", diff --git a/packages/react-meteor-accounts/react-accounts.tests.tsx b/packages/react-meteor-accounts/react-accounts.tests.tsx new file mode 100644 index 00000000..6b77545a --- /dev/null +++ b/packages/react-meteor-accounts/react-accounts.tests.tsx @@ -0,0 +1,122 @@ +import { renderHook } from '@testing-library/react-hooks/dom'; +import { Accounts } from 'meteor/accounts-base'; +import { Meteor } from 'meteor/meteor'; +import { Tinytest } from 'meteor/tinytest'; +import { useLoggingIn, useLoggingOut, useUser, useUserId } from './react-accounts'; + +// Prepare method for clearing DB (doesn't need to be isomorphic). +if (Meteor.isServer) { + Meteor.methods({ + reset() { + const res = Meteor.users.remove({}); + console.log(`method - reset - ${res}`) + return res + }, + }); +} + +if (Meteor.isClient) { + // fixture data + const username = 'username'; + const password = 'password'; + + // common test actions + async function login() { + console.log('before login'); + await new Promise((resolve, reject) => { + Meteor.loginWithPassword(username, password, (error) => { + if (error) reject(error); + else resolve(); + }) + }) + console.log('after login'); + } + async function logout() { + console.log('before logout'); + await new Promise((resolve, reject) => { + Meteor.logout((error) => { + if (error) reject(error); + else resolve(); + }) + }) + console.log('after logout'); + } + + // common test arrangements + async function beforeEach() { + console.log('before beforeEach'); + console.log('beforeEach - before call reset'); + // reset DB; must complete before creation to avoid potential overlap + await new Promise((resolve, reject) => { + Meteor.call('reset', (error, result) => { + console.log(`call - reset - ${result} - ${error}`) + if (error) reject(error); + else resolve(result); + }) + }); + console.log('beforeEach - after call reset'); + // prepare sample user + console.log('beforeEach - before createUser'); + await new Promise((resolve, reject) => { + Accounts.createUser({ username, password }, (error) => { + if (error) reject(error); + else resolve(); + }) + }); + console.log('beforeEach -before createUser'); + // logout since `createUser` auto-logs-in + await logout(); + console.log('after beforeEach'); + } + async function afterEach() { + console.log('before afterEach'); + await logout(); + console.log('after afterEach'); + } + + + // NOTE: each test body has three blocks: Arrange, Act, Assert. + + Tinytest.addAsync('useUserId - has initial value of `null`', async function (test, onComplete) { + console.log('test - before - beforeEach'); + try { + await beforeEach(); + } catch (error) { + console.log('test - catch - beforeEach') + console.error(error) + throw error; + } + console.log('test - after - beforeEach'); + + const { result } = renderHook(() => useUserId()); + + test.isNull(result.current, 'Expect initial value to be `null`'); + onComplete(); + }); + + Tinytest.addAsync('useUserId - is reactive to login', async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useUserId()); + // use `waitFor*` instead of `await`; mimics consumer usage + login(); + await waitForNextUpdate(); + + test.isNotNull(result.current, 'Expect value after login to be not `null`') + onComplete(); + }); + + + Tinytest.addAsync('useUserId - is reactive to logout', async function (test, onComplete) { + await beforeEach(); + await login(); + + const { result, waitForNextUpdate } = renderHook(() => useUserId()); + // use `waitFor*` instead of `await`; mimics consumer usage + logout(); + await waitForNextUpdate(); + + test.isNull(result.current, 'Expect value after logout to be `null`') + onComplete(); + }); +} From 9de28c50f963129c3169b99bc12cf7e58e5411c3 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 15:53:19 -0500 Subject: [PATCH 11/21] test(useLoggingIn): add initial tests --- .../react-accounts.tests.tsx | 76 ++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/packages/react-meteor-accounts/react-accounts.tests.tsx b/packages/react-meteor-accounts/react-accounts.tests.tsx index 6b77545a..685d069e 100644 --- a/packages/react-meteor-accounts/react-accounts.tests.tsx +++ b/packages/react-meteor-accounts/react-accounts.tests.tsx @@ -22,75 +22,50 @@ if (Meteor.isClient) { // common test actions async function login() { - console.log('before login'); await new Promise((resolve, reject) => { - Meteor.loginWithPassword(username, password, (error) => { - if (error) reject(error); - else resolve(); - }) + Meteor.loginWithPassword(username, password, (error) => { + if (error) reject(error); + else resolve(); }) - console.log('after login'); + }) } async function logout() { - console.log('before logout'); await new Promise((resolve, reject) => { Meteor.logout((error) => { if (error) reject(error); else resolve(); }) }) - console.log('after logout'); } // common test arrangements async function beforeEach() { - console.log('before beforeEach'); - console.log('beforeEach - before call reset'); // reset DB; must complete before creation to avoid potential overlap await new Promise((resolve, reject) => { Meteor.call('reset', (error, result) => { - console.log(`call - reset - ${result} - ${error}`) if (error) reject(error); else resolve(result); }) }); - console.log('beforeEach - after call reset'); // prepare sample user - console.log('beforeEach - before createUser'); await new Promise((resolve, reject) => { Accounts.createUser({ username, password }, (error) => { if (error) reject(error); else resolve(); }) }); - console.log('beforeEach -before createUser'); // logout since `createUser` auto-logs-in await logout(); - console.log('after beforeEach'); - } - async function afterEach() { - console.log('before afterEach'); - await logout(); - console.log('after afterEach'); } - // NOTE: each test body has three blocks: Arrange, Act, Assert. Tinytest.addAsync('useUserId - has initial value of `null`', async function (test, onComplete) { - console.log('test - before - beforeEach'); - try { - await beforeEach(); - } catch (error) { - console.log('test - catch - beforeEach') - console.error(error) - throw error; - } - console.log('test - after - beforeEach'); + await beforeEach(); const { result } = renderHook(() => useUserId()); - test.isNull(result.current, 'Expect initial value to be `null`'); + test.isNull(result.current); onComplete(); }); @@ -102,11 +77,10 @@ if (Meteor.isClient) { login(); await waitForNextUpdate(); - test.isNotNull(result.current, 'Expect value after login to be not `null`') + test.isNotNull(result.current) onComplete(); }); - Tinytest.addAsync('useUserId - is reactive to logout', async function (test, onComplete) { await beforeEach(); await login(); @@ -116,7 +90,41 @@ if (Meteor.isClient) { logout(); await waitForNextUpdate(); - test.isNull(result.current, 'Expect value after logout to be `null`') + test.isNull(result.current) + onComplete(); + }); + + Tinytest.addAsync('useLoggingIn - has initial value of `false`', async function (test, onComplete) { + await beforeEach(); + + const { result } = renderHook(() => useLoggingIn()); + + test.isFalse(result.current); + onComplete(); + }); + + Tinytest.addAsync('useLoggingIn - is reactive to login starting', async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useLoggingIn()); + login(); + // first update will be while login strategy is in progress + await waitForNextUpdate(); + + test.isTrue(result.current); + onComplete(); + }); + + Tinytest.addAsync('useLoggingIn - is reactive to login finishing', async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useLoggingIn()); + login(); + await waitForNextUpdate(); + // second update will be after login strategy finishes + await waitForNextUpdate(); + + test.isFalse(result.current); onComplete(); }); } From d93cc5cfb7d6bd710ac884eb91a89fdb3fdf3d41 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 15:53:34 -0500 Subject: [PATCH 12/21] test(useLoggingOut): add initial tests --- .../react-accounts.tests.tsx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/react-meteor-accounts/react-accounts.tests.tsx b/packages/react-meteor-accounts/react-accounts.tests.tsx index 685d069e..8e245aeb 100644 --- a/packages/react-meteor-accounts/react-accounts.tests.tsx +++ b/packages/react-meteor-accounts/react-accounts.tests.tsx @@ -127,4 +127,38 @@ if (Meteor.isClient) { test.isFalse(result.current); onComplete(); }); + + Tinytest.addAsync('useLoggingOut - has initial value of `false`', async function (test, onComplete) { + await beforeEach(); + + const { result } = renderHook(() => useLoggingOut()); + + test.isFalse(result.current); + onComplete(); + }); + + Tinytest.addAsync('useLoggingOut - is reactive to logout starting', async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useLoggingOut()); + logout(); + // first update will be while logout is in progress + await waitForNextUpdate(); + + test.isTrue(result.current); + onComplete(); + }); + + Tinytest.addAsync('useLoggingOut - is reactive to logout finishing', async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useLoggingOut()); + logout(); + await waitForNextUpdate(); + // second update will be after logout finishes + await waitForNextUpdate(); + + test.isFalse(result.current); + onComplete(); + }); } From 9016b54088cad262f69cae8dcded600bd8d499df Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 16:53:41 -0500 Subject: [PATCH 13/21] test(useUser): add initial tests - adjust internal of useUser to fix returning `undefined` after logout (not in signature) --- .../react-accounts.tests.tsx | 39 +++++++++++++++++-- .../react-meteor-accounts/react-accounts.tsx | 7 +++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/react-meteor-accounts/react-accounts.tests.tsx b/packages/react-meteor-accounts/react-accounts.tests.tsx index 8e245aeb..32c320a5 100644 --- a/packages/react-meteor-accounts/react-accounts.tests.tsx +++ b/packages/react-meteor-accounts/react-accounts.tests.tsx @@ -8,9 +8,7 @@ import { useLoggingIn, useLoggingOut, useUser, useUserId } from './react-account if (Meteor.isServer) { Meteor.methods({ reset() { - const res = Meteor.users.remove({}); - console.log(`method - reset - ${res}`) - return res + Meteor.users.remove({}); }, }); } @@ -161,4 +159,39 @@ if (Meteor.isClient) { test.isFalse(result.current); onComplete(); }); + + Tinytest.addAsync('useUser - has initial value of `null`', async function (test, onComplete) { + await beforeEach(); + + const { result } = renderHook(() => useUser()); + + test.isNull(result.current); + onComplete(); + }); + + Tinytest.addAsync('useUser - is reactive to login', async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useUser()); + // use `waitFor*` instead of `await`; mimics consumer usage + login(); + await waitForNextUpdate(); + + test.isNotNull(result.current) + test.equal(result.current.username, username, 'Expected username to match') + onComplete(); + }); + + Tinytest.addAsync('useUser - is reactive to logout', async function (test, onComplete) { + await beforeEach(); + await login(); + + const { result, waitForNextUpdate } = renderHook(() => useUser()); + // use `waitFor*` instead of `await`; mimics consumer usage + logout(); + await waitForNextUpdate(); + + test.isNull(result.current) + onComplete(); + }); } diff --git a/packages/react-meteor-accounts/react-accounts.tsx b/packages/react-meteor-accounts/react-accounts.tsx index 9c4add63..30795f1e 100644 --- a/packages/react-meteor-accounts/react-accounts.tsx +++ b/packages/react-meteor-accounts/react-accounts.tsx @@ -47,7 +47,12 @@ export function useUser(): Meteor.User | null { const [user, setUser] = useState(Meteor.user()) useEffect(() => { const computation = Tracker.autorun(() => { - setUser(Meteor.user()) + let user = Meteor.user(); + // `Meteor.user` returns `undefined` after logout, but that ruins type signature and test parity. So, cast until that's fixed. + if (user === undefined) { + user = null; + } + setUser(user) }) return () => { computation.stop() From 8ae859421c73a9138df8fb28da748a9f74e5f865 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 18:53:36 -0500 Subject: [PATCH 14/21] fix(types): HOC's requiring their inserted prop on wrapper instantiation --- .../react-meteor-accounts/react-accounts.tsx | 136 +++++++++++------- 1 file changed, 85 insertions(+), 51 deletions(-) diff --git a/packages/react-meteor-accounts/react-accounts.tsx b/packages/react-meteor-accounts/react-accounts.tsx index 30795f1e..020806d2 100644 --- a/packages/react-meteor-accounts/react-accounts.tsx +++ b/packages/react-meteor-accounts/react-accounts.tsx @@ -26,25 +26,37 @@ export function useUserId(): string | null { return userId } +export interface WithUserIdProps { + userId: string | null; +} + /** * HOC to forward a stateful value of the current user id. Uses `Meteor.userId`, a reactive data source. * @see https://docs.meteor.com/api/accounts.html#Meteor-userId */ -export function withUserId

(Component: React.ComponentType

) { - return forwardRef((props: P, ref) => { - const userId = useUserId(); - return - }) +export function withUserId

(Component: React.ComponentType

) { + return forwardRef( + // Use `Omit` so instantiation doesn't require the prop. Union with `Partial` because prop should be optionally overridable / the wrapped component will be prepared for it anyways. + (props: Omit & Partial, ref) => { + const userId = useUserId(); + return ( + + ); + } + ); } /** * Hook to get a stateful value of the current user record. Uses `Meteor.user`, a reactive data source. - * @see https://docs.meteor.com/api/accounts.html#Meteor-user + * @see https://docs.meteor.com/api/accounts.html#Meteor-user */ export function useUser(): Meteor.User | null { - const [user, setUser] = useState(Meteor.user()) + const [user, setUser] = useState(Meteor.user()); useEffect(() => { const computation = Tracker.autorun(() => { let user = Meteor.user(); @@ -52,26 +64,30 @@ export function useUser(): Meteor.User | null { if (user === undefined) { user = null; } - setUser(user) - }) + setUser(user); + }); return () => { - computation.stop() - } - }, []) - return user + computation.stop(); + }; + }, []); + return user; +} + +export interface WithUserProps { + user: Meteor.User | null; } /** * HOC to get a stateful value of the current user record. Uses `Meteor.user`, a reactive data source. - * @see https://docs.meteor.com/api/accounts.html#Meteor-user + * @see https://docs.meteor.com/api/accounts.html#Meteor-user */ -export function withUser

(Component: React.ComponentType

) { - return forwardRef((props: P, ref) => { - const user = useUser(); - return - }) +export function withUser

(Component: React.ComponentType

) { + return forwardRef( + (props: Omit & Partial, ref) => { + const user = useUser(); + return ; + } + ); } /** @@ -79,55 +95,73 @@ export function withUser

{ const computation = Tracker.autorun(() => { - setLoggingIn(Meteor.loggingIn()) - }) + setLoggingIn(Meteor.loggingIn()); + }); return () => { - computation.stop() - } - }, []) - return loggingIn + computation.stop(); + }; + }, []); + return loggingIn; +} + +export interface WithLoggingInProps { + loggingIn: boolean; } /** * HOC to forward a stateful value of whether a login method (e.g. `loginWith`) is currently in progress. Uses `Meteor.loggingIn`, a reactive data source. * @see https://docs.meteor.com/api/accounts.html#Meteor-loggingIn */ -export function withLoggingIn

(Component: React.ComponentType

) { - return forwardRef((props: P, ref) => { - const loggingIn = useLoggingIn(); - return - }) +export function withLoggingIn

(Component: React.ComponentType

) { + return forwardRef( + ( + props: Omit & Partial, + ref + ) => { + const loggingIn = useLoggingIn(); + return ( + + ); + } + ); } /** * Hook to get a stateful value of whether the logout method is currently in progress. Uses `Meteor.loggingOut`, a reactive data source. */ export function useLoggingOut(): boolean { - const [loggingOut, setLoggingOut] = useState(Meteor.loggingOut()) + const [loggingOut, setLoggingOut] = useState(Meteor.loggingOut()); useEffect(() => { const computation = Tracker.autorun(() => { - setLoggingOut(Meteor.loggingOut()) - }) + setLoggingOut(Meteor.loggingOut()); + }); return () => { - computation.stop() - } - }, []) - return loggingOut + computation.stop(); + }; + }, []); + return loggingOut; +} + +export interface WithLoggingOutProps { + loggingOut: boolean; } /** * HOC to forward a stateful value of whether the logout method is currently in progress. Uses `Meteor.loggingOut`, a reactive data source. */ -export function withLoggingOut

(Component: React.ComponentType

) { - return forwardRef((props: P, ref) => { - const loggingOut = useLoggingOut(); - return - }) +export function withLoggingOut

(Component: React.ComponentType

) { + return forwardRef( + ( + props: Omit & Partial, + ref + ) => { + const loggingOut = useLoggingOut(); + return ( + + ); + } + ); } From 07daaf87afc21ea51ced53cf48386f4ee01117b7 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 18:53:45 -0500 Subject: [PATCH 15/21] test: add initial tests for HOCs --- .../react-accounts.tests.tsx | 437 ++++++++++++------ 1 file changed, 286 insertions(+), 151 deletions(-) diff --git a/packages/react-meteor-accounts/react-accounts.tests.tsx b/packages/react-meteor-accounts/react-accounts.tests.tsx index 32c320a5..834c8798 100644 --- a/packages/react-meteor-accounts/react-accounts.tests.tsx +++ b/packages/react-meteor-accounts/react-accounts.tests.tsx @@ -1,8 +1,23 @@ import { renderHook } from '@testing-library/react-hooks/dom'; -import { Accounts } from 'meteor/accounts-base'; -import { Meteor } from 'meteor/meteor'; -import { Tinytest } from 'meteor/tinytest'; -import { useLoggingIn, useLoggingOut, useUser, useUserId } from './react-accounts'; +import { cleanup, render, waitFor } from "@testing-library/react"; +import { Accounts } from "meteor/accounts-base"; +import { Meteor } from "meteor/meteor"; +import { Tinytest } from "meteor/tinytest"; +import React from "react"; +import { + useLoggingIn, + useLoggingOut, + useUser, + useUserId, + withLoggingIn, + WithLoggingInProps, + withLoggingOut, + WithLoggingOutProps, + withUser, + withUserId, + WithUserIdProps, + WithUserProps, +} from "./react-accounts"; // Prepare method for clearing DB (doesn't need to be isomorphic). if (Meteor.isServer) { @@ -15,42 +30,42 @@ if (Meteor.isServer) { if (Meteor.isClient) { // fixture data - const username = 'username'; - const password = 'password'; - + const username = "username"; + const password = "password"; + // common test actions async function login() { await new Promise((resolve, reject) => { Meteor.loginWithPassword(username, password, (error) => { if (error) reject(error); else resolve(); - }) - }) + }); + }); } async function logout() { await new Promise((resolve, reject) => { Meteor.logout((error) => { if (error) reject(error); else resolve(); - }) - }) + }); + }); } - + // common test arrangements async function beforeEach() { // reset DB; must complete before creation to avoid potential overlap await new Promise((resolve, reject) => { - Meteor.call('reset', (error, result) => { + Meteor.call("reset", (error, result) => { if (error) reject(error); else resolve(result); - }) + }); }); // prepare sample user await new Promise((resolve, reject) => { Accounts.createUser({ username, password }, (error) => { if (error) reject(error); else resolve(); - }) + }); }); // logout since `createUser` auto-logs-in await logout(); @@ -58,140 +73,260 @@ if (Meteor.isClient) { // NOTE: each test body has three blocks: Arrange, Act, Assert. - Tinytest.addAsync('useUserId - has initial value of `null`', async function (test, onComplete) { - await beforeEach(); - - const { result } = renderHook(() => useUserId()); - - test.isNull(result.current); - onComplete(); - }); - - Tinytest.addAsync('useUserId - is reactive to login', async function (test, onComplete) { - await beforeEach(); - - const { result, waitForNextUpdate } = renderHook(() => useUserId()); - // use `waitFor*` instead of `await`; mimics consumer usage - login(); - await waitForNextUpdate(); - - test.isNotNull(result.current) - onComplete(); - }); - - Tinytest.addAsync('useUserId - is reactive to logout', async function (test, onComplete) { - await beforeEach(); - await login(); - - const { result, waitForNextUpdate } = renderHook(() => useUserId()); - // use `waitFor*` instead of `await`; mimics consumer usage - logout(); - await waitForNextUpdate(); - - test.isNull(result.current) - onComplete(); - }); - - Tinytest.addAsync('useLoggingIn - has initial value of `false`', async function (test, onComplete) { - await beforeEach(); - - const { result } = renderHook(() => useLoggingIn()); - - test.isFalse(result.current); - onComplete(); - }); - - Tinytest.addAsync('useLoggingIn - is reactive to login starting', async function (test, onComplete) { - await beforeEach(); - - const { result, waitForNextUpdate } = renderHook(() => useLoggingIn()); - login(); - // first update will be while login strategy is in progress - await waitForNextUpdate(); - - test.isTrue(result.current); - onComplete(); - }); - - Tinytest.addAsync('useLoggingIn - is reactive to login finishing', async function (test, onComplete) { - await beforeEach(); - - const { result, waitForNextUpdate } = renderHook(() => useLoggingIn()); - login(); - await waitForNextUpdate(); - // second update will be after login strategy finishes - await waitForNextUpdate(); - - test.isFalse(result.current); - onComplete(); - }); - - Tinytest.addAsync('useLoggingOut - has initial value of `false`', async function (test, onComplete) { - await beforeEach(); - - const { result } = renderHook(() => useLoggingOut()); - - test.isFalse(result.current); - onComplete(); - }); - - Tinytest.addAsync('useLoggingOut - is reactive to logout starting', async function (test, onComplete) { - await beforeEach(); - - const { result, waitForNextUpdate } = renderHook(() => useLoggingOut()); - logout(); - // first update will be while logout is in progress - await waitForNextUpdate(); - - test.isTrue(result.current); - onComplete(); - }); - - Tinytest.addAsync('useLoggingOut - is reactive to logout finishing', async function (test, onComplete) { - await beforeEach(); - - const { result, waitForNextUpdate } = renderHook(() => useLoggingOut()); - logout(); - await waitForNextUpdate(); - // second update will be after logout finishes - await waitForNextUpdate(); - - test.isFalse(result.current); - onComplete(); - }); - - Tinytest.addAsync('useUser - has initial value of `null`', async function (test, onComplete) { - await beforeEach(); - - const { result } = renderHook(() => useUser()); - - test.isNull(result.current); - onComplete(); - }); - - Tinytest.addAsync('useUser - is reactive to login', async function (test, onComplete) { - await beforeEach(); - - const { result, waitForNextUpdate } = renderHook(() => useUser()); - // use `waitFor*` instead of `await`; mimics consumer usage - login(); - await waitForNextUpdate(); - - test.isNotNull(result.current) - test.equal(result.current.username, username, 'Expected username to match') - onComplete(); - }); - - Tinytest.addAsync('useUser - is reactive to logout', async function (test, onComplete) { - await beforeEach(); - await login(); - - const { result, waitForNextUpdate } = renderHook(() => useUser()); - // use `waitFor*` instead of `await`; mimics consumer usage - logout(); - await waitForNextUpdate(); - - test.isNull(result.current) - onComplete(); - }); + Tinytest.addAsync( + "Hooks - useUserId - has initial value of `null`", + async function (test, onComplete) { + await beforeEach(); + + const { result } = renderHook(() => useUserId()); + + test.isNull(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useUserId - is reactive to login", + async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useUserId()); + // use `waitFor*` instead of `await`; mimics consumer usage + login(); + await waitForNextUpdate(); + + test.isNotNull(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useUserId - is reactive to logout", + async function (test, onComplete) { + await beforeEach(); + await login(); + + const { result, waitForNextUpdate } = renderHook(() => useUserId()); + // use `waitFor*` instead of `await`; mimics consumer usage + logout(); + await waitForNextUpdate(); + + test.isNull(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useLoggingIn - has initial value of `false`", + async function (test, onComplete) { + await beforeEach(); + + const { result } = renderHook(() => useLoggingIn()); + + test.isFalse(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useLoggingIn - is reactive to login starting", + async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useLoggingIn()); + login(); + // first update will be while login strategy is in progress + await waitForNextUpdate(); + + test.isTrue(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useLoggingIn - is reactive to login finishing", + async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useLoggingIn()); + login(); + await waitForNextUpdate(); + // second update will be after login strategy finishes + await waitForNextUpdate(); + + test.isFalse(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useLoggingOut - has initial value of `false`", + async function (test, onComplete) { + await beforeEach(); + + const { result } = renderHook(() => useLoggingOut()); + + test.isFalse(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useLoggingOut - is reactive to logout starting", + async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useLoggingOut()); + logout(); + // first update will be while logout is in progress + await waitForNextUpdate(); + + test.isTrue(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useLoggingOut - is reactive to logout finishing", + async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useLoggingOut()); + logout(); + await waitForNextUpdate(); + // second update will be after logout finishes + await waitForNextUpdate(); + + test.isFalse(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useUser - has initial value of `null`", + async function (test, onComplete) { + await beforeEach(); + + const { result } = renderHook(() => useUser()); + + test.isNull(result.current); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useUser - is reactive to login", + async function (test, onComplete) { + await beforeEach(); + + const { result, waitForNextUpdate } = renderHook(() => useUser()); + // use `waitFor*` instead of `await`; mimics consumer usage + login(); + await waitForNextUpdate(); + + test.isNotNull(result.current); + test.equal( + result.current.username, + username, + "Expected username to match" + ); + onComplete(); + } + ); + + Tinytest.addAsync( + "Hooks - useUser - is reactive to logout", + async function (test, onComplete) { + await beforeEach(); + await login(); + + const { result, waitForNextUpdate } = renderHook(() => useUser()); + // use `waitFor*` instead of `await`; mimics consumer usage + logout(); + await waitForNextUpdate(); + + test.isNull(result.current); + onComplete(); + } + ); + + // Since the HOCs wrap with hooks, the logic is already tested in 'Hooks' tests, and we only really need to test for prop forwarding. However, doing so for the "non-initial" case of all these values seems more prudent than just checking the default of `null` or `false`. + + // :NOTE: these tests can be flaky (like 1 in 5 runs). + + Tinytest.addAsync( + "HOCs - withUserId - forwards reactive value", + async function (test, onComplete) { + await beforeEach(); + function Foo({ userId }: WithUserIdProps) { + // need something we can easily find; we don't know the id + return {Boolean(userId).toString()}; + } + const FooWithUserId = withUserId(Foo); + const { findByText } = render(); + + login(); + + await waitFor(() => findByText("true")); + cleanup(); + onComplete(); + } + ); + + // :TODO: this is flaky, fails ~1 in 10 + Tinytest.addAsync( + "HOCs - withUser - forwards reactive value", + async function (test, onComplete) { + await beforeEach(); + function Foo({ user }: WithUserProps) { + return {user?.username || String(user)}; + } + const FooWithUser = withUser(Foo); + const { findByText } = render(); + + login(); + + await waitFor(() => findByText(username)); + cleanup(); + onComplete(); + } + ); + + Tinytest.addAsync( + "HOCs - withLoggingIn - forwards reactive value", + async function (test, onComplete) { + await beforeEach(); + function Foo({ loggingIn }: WithLoggingInProps) { + return {loggingIn.toString()}; + } + const FooWithLoggingIn = withLoggingIn(Foo); + const { findByText } = render(); + + login(); + + await waitFor(() => findByText("true")); + cleanup(); + onComplete(); + } + ); + + // :TODO: this is flaky, fails ~1 in 5 + Tinytest.addAsync( + "HOCs - withLoggingOut - forwards reactive value", + async function (test, onComplete) { + await beforeEach(); + function Foo({ loggingOut }: WithLoggingOutProps) { + return {loggingOut.toString()}; + } + const FooWithLoggingOut = withLoggingOut(Foo); + const { findByText } = render(); + await login(); + + logout(); + + await waitFor(() => findByText("true")); + cleanup(); + onComplete(); + } + ); } From a235d5bd8e836b89d80c65a986c9c46e3f66a1a5 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 19:24:09 -0500 Subject: [PATCH 16/21] chore(docs): add sections for hooks/HOCs of loggingIn/Out --- packages/react-meteor-accounts/README.md | 136 +++++++++++++++++- packages/react-meteor-accounts/package.js | 1 - .../types/react-accounts.d.ts | 55 ++++++- 3 files changed, 185 insertions(+), 7 deletions(-) diff --git a/packages/react-meteor-accounts/README.md b/packages/react-meteor-accounts/README.md index 1e1e1ca9..d5c30154 100644 --- a/packages/react-meteor-accounts/README.md +++ b/packages/react-meteor-accounts/README.md @@ -41,7 +41,7 @@ _Note:_ All HOCs forward refs. ### useUser() / withUser(...) -Get a stateful value of the current user record from [`Meteor.user`](https://docs.meteor.com/api/accounts.html#Meteor-user), a reactive data source. +Get a stateful value of the current user record. Uses [`Meteor.user`](https://docs.meteor.com/api/accounts.html#Meteor-user), a reactive data source. The hook, `useUser()`, returns a stateful value of the current user record. @@ -96,12 +96,12 @@ TypeScript signatures: function useUser(): Meteor.User | null; // HOC -function withUser

(Component: React.ComponentType

): React.ForwardRefExoticComponent & React.RefAttributes>; +function withUser

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; ``` ### useUserId() / withUserId(...) -Get a stateful value of the current user id from [`Meteor.userId`](https://docs.meteor.com/api/accounts.html#Meteor-userId), a reactive data source. +Get a stateful value of the current user id. Uses [`Meteor.userId`](https://docs.meteor.com/api/accounts.html#Meteor-userId), a reactive data source. The hook, `useUserId()`, returns a stateful value of the current user id. @@ -166,5 +166,133 @@ TypeScript signatures: function useUserId(): string | null; // HOC -function withUserId

(Component: React.ComponentType

): React.ForwardRefExoticComponent & React.RefAttributes>; +function withUserId

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; +``` + +### useLoggingIn() / withLoggingIn(...) + +Get a stateful value of whether a login method (e.g. `loginWith`) is currently in progress. Uses [`Meteor.loggingIn`](https://docs.meteor.com/api/accounts.html#Meteor-loggingIn), a reactive data source. + +The hook, `useLoggingIn()`, returns the stateful value. + +- Arguments: *none*. +- Returns: `boolean`. + +The HOC, `withLoggingIn(Component)`, returns a wrapped version of `Component`, where `Component` receives a prop of the stateful value, `loggingIn`. + +- Arguments: + +| Argument | Type | Required | Description | +| --- | --- | --- | --- | +| Component | `React.ComponentType` | yes | A React component. | + +- Returns: `React.ForwardRefExoticComponent`. + +Examples: + +```tsx +import React from 'react'; +import { useLoggingIn, withLoggingIn } from 'meteor/react-meteor-accounts'; + +// Hook +function Foo() { + const loggingIn = useLoggingIn(); + + if (!loggingIn) { + return null; + } + + return ( +

Logging in, please wait a moment.
+ ); +} + +// HOC +class Bar extends React.Component { + render() { + if (!this.props.loggingIn) { + return null; + } + + return ( +
Logging in, please wait a moment.
+ ); + } +} + +const BarWithLoggingIn = withLoggingIn(Bar); +``` + +TypeScript signatures: + +```ts +// Hook +function useLoggingIn(): boolean; + +// HOC +function withLoggingIn

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; +``` + +### useLoggingOut() / withLoggingOut(...) + +Get a stateful value of whether the logout method is currently in progress. Uses `Meteor.loggingOut` (no online documentation), a reactive data source. + +The hook, `useLoggingOut()`, returns the stateful value. + +- Arguments: *none*. +- Returns: `boolean`. + +The HOC, `withLoggingOut(Component)`, returns a wrapped version of `Component`, where `Component` receives a prop of the stateful value, `loggingOut`. + +- Arguments: + +| Argument | Type | Required | Description | +| --- | --- | --- | --- | +| Component | `React.ComponentType` | yes | A React component. | + +- Returns: `React.ForwardRefExoticComponent`. + +Examples: + +```tsx +import React from 'react'; +import { useLoggingOut, withLoggingOut } from 'meteor/react-meteor-accounts'; + +// Hook +function Foo() { + const loggingOut = useLoggingOut(); + + if (!loggingOut) { + return null; + } + + return ( +

Logging out in, please wait a moment.
+ ); +} + +// HOC +class Bar extends React.Component { + render() { + if (!this.props.loggingOut) { + return null; + } + + return ( +
Logging out, please wait a moment.
+ ); + } +} + +const BarWithLoggingOut = withLoggingOut(Bar); +``` + +TypeScript signatures: + +```ts +// Hook +function useLoggingOut(): boolean; + +// HOC +function withLoggingOut

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; ``` diff --git a/packages/react-meteor-accounts/package.js b/packages/react-meteor-accounts/package.js index ec90f6be..8facc24f 100644 --- a/packages/react-meteor-accounts/package.js +++ b/packages/react-meteor-accounts/package.js @@ -20,7 +20,6 @@ Package.onTest((api) => { api.use([ 'accounts-base', 'accounts-password', - 'react-accounts', 'tinytest', 'tracker', 'typescript', diff --git a/packages/react-meteor-accounts/types/react-accounts.d.ts b/packages/react-meteor-accounts/types/react-accounts.d.ts index b813f3db..0a90ab56 100644 --- a/packages/react-meteor-accounts/types/react-accounts.d.ts +++ b/packages/react-meteor-accounts/types/react-accounts.d.ts @@ -1,6 +1,57 @@ import { Meteor } from 'meteor/meteor'; import React from 'react'; +declare module 'meteor/meteor' { + module Meteor { + function loggingOut(): boolean; + } +} +/** + * Hook to get a stateful value of the current user id. Uses `Meteor.userId`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-userId + */ export declare function useUserId(): string | null; -export declare function withUserId

(Component: React.ComponentType

): React.ForwardRefExoticComponent & React.RefAttributes>; +export interface WithUserIdProps { + userId: string | null; +} +/** + * HOC to forward a stateful value of the current user id. Uses `Meteor.userId`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-userId + */ +export declare function withUserId

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; +/** + * Hook to get a stateful value of the current user record. Uses `Meteor.user`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-user + */ export declare function useUser(): Meteor.User | null; -export declare function withUser

(Component: React.ComponentType

): React.ForwardRefExoticComponent & React.RefAttributes>; +export interface WithUserProps { + user: Meteor.User | null; +} +/** + * HOC to get a stateful value of the current user record. Uses `Meteor.user`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-user + */ +export declare function withUser

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; +/** + * Hook to get a stateful value of whether a login method (e.g. `loginWith`) is currently in progress. Uses `Meteor.loggingIn`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-loggingIn + */ +export declare function useLoggingIn(): boolean; +export interface WithLoggingInProps { + loggingIn: boolean; +} +/** + * HOC to forward a stateful value of whether a login method (e.g. `loginWith`) is currently in progress. Uses `Meteor.loggingIn`, a reactive data source. + * @see https://docs.meteor.com/api/accounts.html#Meteor-loggingIn + */ +export declare function withLoggingIn

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; +/** + * Hook to get a stateful value of whether the logout method is currently in progress. Uses `Meteor.loggingOut`, a reactive data source. + */ +export declare function useLoggingOut(): boolean; +export interface WithLoggingOutProps { + loggingOut: boolean; +} +/** + * HOC to forward a stateful value of whether the logout method is currently in progress. Uses `Meteor.loggingOut`, a reactive data source. + */ +export declare function withLoggingOut

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; From da753265e6e3bc401b36708dcfd911debaeeea37 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 19:43:19 -0500 Subject: [PATCH 17/21] chore(docs): split docs of hooks and HOCs --- packages/react-meteor-accounts/README.md | 282 +++++++++++++---------- 1 file changed, 158 insertions(+), 124 deletions(-) diff --git a/packages/react-meteor-accounts/README.md b/packages/react-meteor-accounts/README.md index d5c30154..d0c04f0f 100644 --- a/packages/react-meteor-accounts/README.md +++ b/packages/react-meteor-accounts/README.md @@ -8,8 +8,14 @@ Simple hooks and higher-order components (HOCs) for getting reactive, stateful v - [Peer npm dependencies](#peer-npm-dependencies) - [Changelog](#changelog) - [Usage](#usage) - - [`useUser` / `withUser`](#useuser--withUser) - - [`useUserId` / `withUserId`](#useuserid--withUserId) + - [`useUser`](#useuser) + - [`useUserId`](#useuserid) + - [`useLoggingIn`](#useloggingin) + - [`useLoggingOut`](#useloggingout) + - [`withUser`](#withuser) + - [`withUserId`](#withuserid) + - [`withLoggingIn`](#withloggingin) + - [`withLoggingOut`](#withloggingout) ## Installation @@ -39,32 +45,19 @@ Utilities for each data source are available for the two ways of writing React c _Note:_ All HOCs forward refs. -### useUser() / withUser(...) +### useUser() -Get a stateful value of the current user record. Uses [`Meteor.user`](https://docs.meteor.com/api/accounts.html#Meteor-user), a reactive data source. - -The hook, `useUser()`, returns a stateful value of the current user record. +Get a stateful value of the current user record. A hook. Uses [`Meteor.user`](https://docs.meteor.com/api/accounts.html#Meteor-user), a reactive data source. - Arguments: *none*. - Returns: `object | null`. -The HOC, `withUser(Component)`, returns a wrapped version of `Component`, where `Component` receives a prop of the current user record, `user`. - -- Arguments: - -| Argument | Type | Required | Description | -| --- | --- | --- | --- | -| Component | `any` | yes | A React component. | - -- Returns: `React.ForwardRefExoticComponent`. - -Examples: +Example: ```tsx import React from 'react'; -import { useUser, withUser } from 'meteor/react-meteor-accounts'; +import { useUser } from 'meteor/react-meteor-accounts'; -// Hook function Foo() { const user = useUser(); @@ -74,41 +67,116 @@ function Foo() { return

Hello {user.username}

; } +``` -// HOC -class Bar extends React.Component { - render() { - if (this.props.user === null) { - return

Log in

; - } +TypeScript signature: - return

Hello {this.props.user.username}

; - } -} +```ts +function useUser(): Meteor.User | null; +``` + +### useUserId() + +Get a stateful value of the current user id. A hook. Uses [`Meteor.userId`](https://docs.meteor.com/api/accounts.html#Meteor-userId), a reactive data source. -const WrappedBar = withUser(Bar); +- Arguments: *none*. +- Returns: `string | null`. + +Example: + +```tsx +import React from 'react'; +import { useUserId } from 'meteor/react-meteor-accounts'; + +function Foo() { + const userId = useUserId(); + + return ( +
+

Account Details

+ {userId ? ( +

Your unique account id is {userId}.

+ ) : ( +

Log-in to view your account details.

+ )} +
+ ); +} ``` -TypeScript signatures: +TypeScript signature: ```ts -// Hook -function useUser(): Meteor.User | null; +function useUserId(): string | null; +``` -// HOC -function withUser

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; +### useLoggingIn() + +Get a stateful value of whether a login method (e.g. `loginWith`) is currently in progress. A hook. Uses [`Meteor.loggingIn`](https://docs.meteor.com/api/accounts.html#Meteor-loggingIn), a reactive data source. + +- Arguments: *none*. +- Returns: `boolean`. + +Example: + +```tsx +import React from 'react'; +import { useLoggingIn } from 'meteor/react-meteor-accounts'; + +function Foo() { + const loggingIn = useLoggingIn(); + + if (!loggingIn) { + return null; + } + + return ( +

Logging in, please wait a moment.
+ ); +} ``` -### useUserId() / withUserId(...) +TypeScript signature: + +```ts +function useLoggingIn(): boolean; +``` -Get a stateful value of the current user id. Uses [`Meteor.userId`](https://docs.meteor.com/api/accounts.html#Meteor-userId), a reactive data source. +### useLoggingOut() -The hook, `useUserId()`, returns a stateful value of the current user id. +Get a stateful value of whether the logout method is currently in progress. A hook. Uses `Meteor.loggingOut` (no online documentation), a reactive data source. - Arguments: *none*. -- Returns: `string | null`. +- Returns: `boolean`. + +Example: + +```tsx +import React from 'react'; +import { useLoggingOut } from 'meteor/react-meteor-accounts'; -The HOC, `withUserId(Component)`, returns a wrapped version of `Component`, where `Component` receives a prop of the current user id, `userId`. +function Foo() { + const loggingOut = useLoggingOut(); + + if (!loggingOut) { + return null; + } + + return ( +
Logging out, please wait a moment.
+ ); +} +``` + +TypeScript signature: + +```ts +function useLoggingOut(): boolean; +``` + +### withUser(...) + +Return a wrapped version of the given component, where the component receives a stateful prop of the current user record, `user`. A higher-order component. Uses [`Meteor.user`](https://docs.meteor.com/api/accounts.html#Meteor-user), a reactive data source. - Arguments: @@ -122,26 +190,46 @@ Examples: ```tsx import React from 'react'; -import { useUserId, withUserId } from 'meteor/react-meteor-accounts'; +import { withUser } from 'meteor/react-meteor-accounts'; -// Hook -function Foo() { - const userId = useUserId(); +class Foo extends React.Component { + render() { + if (this.props.user === null) { + return

Log in

; + } - return ( -
-

Account Details

- {userId ? ( -

Your unique account id is {userId}.

- ) : ( -

Log-in to view your account details.

- )} -
- ); + return

Hello {this.props.user.username}

; + } } -// HOC -class Bar extends React.Component { +const FooWithUser = withUser(Foo); +``` + +TypeScript signature: + +```ts +function withUser

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; +``` + +### withUserId(...) + +Return a wrapped version of the given component, where the component receives a stateful prop of the current user id. A higher-order component. Uses [`Meteor.userId`](https://docs.meteor.com/api/accounts.html#Meteor-userId), a reactive data source. + +- Arguments: + +| Argument | Type | Required | Description | +| --- | --- | --- | --- | +| Component | `React.ComponentType` | yes | A React component. | + +- Returns: `React.ForwardRefExoticComponent`. + +Example: + +```tsx +import React from 'react'; +import { withUserId } from 'meteor/react-meteor-accounts'; + +class Foo extends React.Component { render() { return (

@@ -156,29 +244,18 @@ class Bar extends React.Component { } } -const WrappedBar = withUserId(Bar); +const FooWithUserId = withUserId(Foo); ``` -TypeScript signatures: +TypeScript signature: ```ts -// Hook -function useUserId(): string | null; - -// HOC function withUserId

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; ``` -### useLoggingIn() / withLoggingIn(...) - -Get a stateful value of whether a login method (e.g. `loginWith`) is currently in progress. Uses [`Meteor.loggingIn`](https://docs.meteor.com/api/accounts.html#Meteor-loggingIn), a reactive data source. +### withLoggingIn(...) -The hook, `useLoggingIn()`, returns the stateful value. - -- Arguments: *none*. -- Returns: `boolean`. - -The HOC, `withLoggingIn(Component)`, returns a wrapped version of `Component`, where `Component` receives a prop of the stateful value, `loggingIn`. +Return a wrapped version of the given component, where the component receives a stateful prop of whether a login method (e.g. `loginWith`) is currently in progress. A higher-order component. Uses [`Meteor.loggingIn`](https://docs.meteor.com/api/accounts.html#Meteor-loggingIn), a reactive data source. - Arguments: @@ -188,27 +265,13 @@ The HOC, `withLoggingIn(Component)`, returns a wrapped version of `Component`, w - Returns: `React.ForwardRefExoticComponent`. -Examples: +Example: ```tsx import React from 'react'; -import { useLoggingIn, withLoggingIn } from 'meteor/react-meteor-accounts'; - -// Hook -function Foo() { - const loggingIn = useLoggingIn(); - - if (!loggingIn) { - return null; - } - - return ( -

Logging in, please wait a moment.
- ); -} +import { withLoggingIn } from 'meteor/react-meteor-accounts'; -// HOC -class Bar extends React.Component { +class Foo extends React.Component { render() { if (!this.props.loggingIn) { return null; @@ -220,29 +283,18 @@ class Bar extends React.Component { } } -const BarWithLoggingIn = withLoggingIn(Bar); +const FooWithLoggingIn = withLoggingIn(Foo); ``` TypeScript signatures: ```ts -// Hook -function useLoggingIn(): boolean; - -// HOC function withLoggingIn

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; ``` -### useLoggingOut() / withLoggingOut(...) - -Get a stateful value of whether the logout method is currently in progress. Uses `Meteor.loggingOut` (no online documentation), a reactive data source. +### withLoggingOut(...) -The hook, `useLoggingOut()`, returns the stateful value. - -- Arguments: *none*. -- Returns: `boolean`. - -The HOC, `withLoggingOut(Component)`, returns a wrapped version of `Component`, where `Component` receives a prop of the stateful value, `loggingOut`. +Return a wrapped version of the given component, where the component receives a stateful prop of whether the logout method is currently in progress. A higher-order component. Uses `Meteor.loggingOut` (no online documentation), a reactive data source. - Arguments: @@ -252,27 +304,13 @@ The HOC, `withLoggingOut(Component)`, returns a wrapped version of `Component`, - Returns: `React.ForwardRefExoticComponent`. -Examples: +Example: ```tsx import React from 'react'; -import { useLoggingOut, withLoggingOut } from 'meteor/react-meteor-accounts'; - -// Hook -function Foo() { - const loggingOut = useLoggingOut(); +import { withLoggingOut } from 'meteor/react-meteor-accounts'; - if (!loggingOut) { - return null; - } - - return ( -

Logging out in, please wait a moment.
- ); -} - -// HOC -class Bar extends React.Component { +class Foo extends React.Component { render() { if (!this.props.loggingOut) { return null; @@ -284,15 +322,11 @@ class Bar extends React.Component { } } -const BarWithLoggingOut = withLoggingOut(Bar); +const FooWithLoggingOut = withLoggingOut(Foo); ``` -TypeScript signatures: +TypeScript signature: ```ts -// Hook -function useLoggingOut(): boolean; - -// HOC function withLoggingOut

(Component: React.ComponentType

): React.ForwardRefExoticComponent & Partial> & React.RefAttributes>; ``` From 585f1f72ac2ba8846053ed6a69fbad454a879474 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 23:09:56 -0500 Subject: [PATCH 18/21] fix: re-export all utils from index --- packages/react-meteor-accounts/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/react-meteor-accounts/index.ts b/packages/react-meteor-accounts/index.ts index 074d0fd5..e5800631 100644 --- a/packages/react-meteor-accounts/index.ts +++ b/packages/react-meteor-accounts/index.ts @@ -9,4 +9,13 @@ if (Meteor.isDevelopment) { } } -export { useUser, withUser, useUserId, withUserId } from './react-accounts'; +export { + useUser, + useUserId, + useLoggingIn, + useLoggingOut, + withUser, + withUserId, + withLoggingIn, + withLoggingOut +} from './react-accounts'; From 81ef37d326be3a06885366c98facb638e15459be Mon Sep 17 00:00:00 2001 From: William Kelley Date: Tue, 14 Dec 2021 23:11:36 -0500 Subject: [PATCH 19/21] fix: correct package name in warning --- packages/react-meteor-accounts/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-meteor-accounts/index.ts b/packages/react-meteor-accounts/index.ts index e5800631..a7312bdf 100644 --- a/packages/react-meteor-accounts/index.ts +++ b/packages/react-meteor-accounts/index.ts @@ -5,7 +5,7 @@ if (Meteor.isDevelopment) { // Custom check instead of `checkNpmVersions` to reduce prod bundle size (~8kb). const v = React.version.split('.').map(val => parseInt(val)); if (v[0] < 16 || (v[0] === 16 && v[1] < 8)) { - console.warn('react-meteor-accounts requires React version >= 16.8.'); + console.warn('react-accounts requires React version >= 16.8.'); } } From 4ba4ae48bfad53dd745ec660cf6e3e02086315bc Mon Sep 17 00:00:00 2001 From: William Kelley Date: Wed, 22 Dec 2021 00:21:03 -0700 Subject: [PATCH 20/21] fix(internal): missing null union in return types --- packages/react-meteor-accounts/package-lock.json | 6 ++++++ packages/react-meteor-accounts/package.json | 3 ++- packages/react-meteor-accounts/react-accounts.tsx | 6 +++--- packages/react-meteor-accounts/tsconfig.json | 6 ++++++ 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 packages/react-meteor-accounts/tsconfig.json diff --git a/packages/react-meteor-accounts/package-lock.json b/packages/react-meteor-accounts/package-lock.json index abdc6559..da1779fa 100644 --- a/packages/react-meteor-accounts/package-lock.json +++ b/packages/react-meteor-accounts/package-lock.json @@ -124,6 +124,12 @@ } } }, + "@tsconfig/recommended": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/recommended/-/recommended-1.0.1.tgz", + "integrity": "sha512-2xN+iGTbPBEzGSnVp/Hd64vKJCJWxsi9gfs88x4PPMyEjHJoA3o5BY9r5OLPHIZU2pAQxkSAsJFqn6itClP8mQ==", + "dev": true + }, "@types/aria-query": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", diff --git a/packages/react-meteor-accounts/package.json b/packages/react-meteor-accounts/package.json index 8dc3e3e6..4f38b650 100644 --- a/packages/react-meteor-accounts/package.json +++ b/packages/react-meteor-accounts/package.json @@ -1,11 +1,12 @@ { "name": "meteor-react-accounts", "scripts": { - "make-types": "npx typescript *.tsx --jsx preserve --declaration --emitDeclarationOnly --esModuleInterop --outDir types" + "make-types": "npx typescript react-accounts.tsx --jsx preserve --declaration --emitDeclarationOnly --esModuleInterop --outDir types --strict" }, "devDependencies": { "@testing-library/react": "^10.0.2", "@testing-library/react-hooks": "^7.0.2", + "@tsconfig/recommended": "^1.0.1", "@types/meteor": "^1.4.42", "@types/react": "^16.9.34", "react": "16.13.1", diff --git a/packages/react-meteor-accounts/react-accounts.tsx b/packages/react-meteor-accounts/react-accounts.tsx index 020806d2..f96d3217 100644 --- a/packages/react-meteor-accounts/react-accounts.tsx +++ b/packages/react-meteor-accounts/react-accounts.tsx @@ -13,7 +13,7 @@ declare module 'meteor/meteor' { * Hook to get a stateful value of the current user id. Uses `Meteor.userId`, a reactive data source. * @see https://docs.meteor.com/api/accounts.html#Meteor-userId */ -export function useUserId(): string | null { +export function useUserId() { const [userId, setUserId] = useState(Meteor.userId()) useEffect(() => { const computation = Tracker.autorun(() => { @@ -55,7 +55,7 @@ export function withUserId

(Component: React.ComponentType

) { * Hook to get a stateful value of the current user record. Uses `Meteor.user`, a reactive data source. * @see https://docs.meteor.com/api/accounts.html#Meteor-user */ -export function useUser(): Meteor.User | null { +export function useUser() { const [user, setUser] = useState(Meteor.user()); useEffect(() => { const computation = Tracker.autorun(() => { @@ -74,7 +74,7 @@ export function useUser(): Meteor.User | null { } export interface WithUserProps { - user: Meteor.User | null; + user: Meteor.User; } /** diff --git a/packages/react-meteor-accounts/tsconfig.json b/packages/react-meteor-accounts/tsconfig.json new file mode 100644 index 00000000..1cb0c5fb --- /dev/null +++ b/packages/react-meteor-accounts/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@tsconfig/recommended/tsconfig.json", + "compilerOptions": { + "jsx": "preserve" + } +} \ No newline at end of file From 667b94fa4903024198863261dc54d6d5e06eef68 Mon Sep 17 00:00:00 2001 From: William Kelley Date: Thu, 23 Dec 2021 15:59:08 -0700 Subject: [PATCH 21/21] fix(types): remove unnecessary module aug. & add null union for `user` --- packages/react-meteor-accounts/react-accounts.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/react-meteor-accounts/react-accounts.tsx b/packages/react-meteor-accounts/react-accounts.tsx index f96d3217..13516461 100644 --- a/packages/react-meteor-accounts/react-accounts.tsx +++ b/packages/react-meteor-accounts/react-accounts.tsx @@ -2,13 +2,6 @@ import { Meteor } from 'meteor/meteor' import { Tracker } from 'meteor/tracker' import React, { useState, useEffect, forwardRef } from 'react' -// Augmentation to add missing signature -declare module 'meteor/meteor' { - module Meteor { - function loggingOut(): boolean; - } -} - /** * Hook to get a stateful value of the current user id. Uses `Meteor.userId`, a reactive data source. * @see https://docs.meteor.com/api/accounts.html#Meteor-userId @@ -74,7 +67,7 @@ export function useUser() { } export interface WithUserProps { - user: Meteor.User; + user: Meteor.User | null; } /**