Skip to content

Commit

Permalink
Separate user state from userRecord
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwoodnz committed May 22, 2023
1 parent 1457442 commit 8f430ee
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 80 deletions.
5 changes: 3 additions & 2 deletions settings/src/components/account-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ import ScreenLink from './screen-link';
* Render the Account Status.
*/
export default function AccountStatus() {
const { userRecord } = useContext( GlobalContext );
const {
user: { userRecord, hasPrimaryProvider },
} = useContext( GlobalContext );
const {
record: {
'2fa_available_providers': availableProviders,
email,
pending_email: pendingEmail,
},
hasPrimaryProvider,
} = userRecord;
const emailStatus = pendingEmail ? 'pending' : 'ok';
const totpEnabled = availableProviders.includes( 'Two_Factor_Totp' );
Expand Down
13 changes: 10 additions & 3 deletions settings/src/components/backup-codes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import { refreshRecord } from '../utilities';
* Setup and manage backup codes.
*/
export default function BackupCodes() {
const { userRecord } = useContext( GlobalContext );
const {
user: { userRecord },
} = useContext( GlobalContext );
const [ regenerating, setRegenerating ] = useState( false );

const backupCodesEnabled =
Expand All @@ -35,7 +37,10 @@ export default function BackupCodes() {
* @param props.setRegenerating
*/
function Setup( { setRegenerating } ) {
const { setGlobalNotice, userRecord } = useContext( GlobalContext );
const {
setGlobalNotice,
user: { userRecord },
} = useContext( GlobalContext );
const [ backupCodes, setBackupCodes ] = useState( [] );
const [ hasPrinted, setHasPrinted ] = useState( false );

Expand Down Expand Up @@ -142,7 +147,9 @@ function CodeList( { codes } ) {
* @param props.setRegenerating
*/
function Manage( { setRegenerating } ) {
const { userRecord } = useContext( GlobalContext );
const {
user: { userRecord },
} = useContext( GlobalContext );
const remaining = userRecord.record[ '2fa_backup_codes_remaining' ];

return (
Expand Down
6 changes: 4 additions & 2 deletions settings/src/components/email-address.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import { GlobalContext } from '../script';
* Render the Email setting.
*/
export default function EmailAddress() {
const { userRecord } = useContext( GlobalContext );
const { record, edit, save, editedRecord, hasEdits, isSaving } = userRecord;
const {
user: { userRecord, isSaving },
} = useContext( GlobalContext );
const { record, edit, save, editedRecord, hasEdits } = userRecord;
const [ emailError, setEmailError ] = useState( '' );
const [ justChangedEmail, setJustChangedEmail ] = useState( false );

Expand Down
11 changes: 7 additions & 4 deletions settings/src/components/password.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import { GlobalContext } from '../script';
* Render the Password setting.
*/
export default function Password() {
const { setGlobalNotice, userRecord } = useContext( GlobalContext );
const {
setGlobalNotice,
user: { userRecord, isSaving },
} = useContext( GlobalContext );
const [ inputType, setInputType ] = useState( 'password' );
const [ hasAttemptedSave, setHasAttemptedSave ] = useState( false );
let passwordStrong = true; // Saved passwords have already passed the test.
Expand Down Expand Up @@ -58,7 +61,7 @@ export default function Password() {

setHasAttemptedSave( true );

if ( ! passwordStrong || userRecord.isSaving ) {
if ( ! passwordStrong || isSaving ) {
return;
}

Expand All @@ -74,7 +77,7 @@ export default function Password() {

setGlobalNotice( 'New password saved.' );
},
[ passwordStrong, userRecord.isSaving ]
[ passwordStrong, isSaving ]
);

const handlePasswordChange = useCallback( ( password ) => userRecord.edit( { password } ), [] );
Expand Down Expand Up @@ -144,7 +147,7 @@ export default function Password() {

<p>
<Button isPrimary disabled={ ! userRecord.editedRecord.password } type="submit">
{ userRecord.isSaving ? 'Saving...' : 'Save password' }
{ isSaving ? 'Saving...' : 'Save password' }
</Button>

{ window.crypto?.getRandomValues && (
Expand Down
5 changes: 4 additions & 1 deletion settings/src/components/revalidate-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ export default function RevalidateModal() {
}

function RevalidateIframe() {
const { setGlobalNotice, userRecord } = useContext( GlobalContext );
const {
setGlobalNotice,
user: { userRecord },
} = useContext( GlobalContext );
const ref = useRef();

useEffect( () => {
Expand Down
42 changes: 22 additions & 20 deletions settings/src/components/tests/password/password.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ apiFetch.nonceMiddleware = { nonce: '' };

// Default mock context
const mockContext = {
userRecord: {
editedRecord: {
password: 'password',
},
edit: jest.fn(),
save: jest.fn(),
hasEdits: false,
record: {
'2fa_required': true,
user: {
userRecord: {
editedRecord: {
password: 'password',
},
edit: jest.fn(),
save: jest.fn(),
hasEdits: false,
record: {
'2fa_required': true,
},
},
isSaving: false,
},
Expand All @@ -57,8 +59,8 @@ describe( 'Password', () => {
} );

afterEach( () => {
mockContext.userRecord.edit.mockReset();
mockContext.userRecord.save.mockReset();
mockContext.user.userRecord.edit.mockReset();
mockContext.user.userRecord.save.mockReset();
mockContext.setGlobalNotice.mockReset();
} );

Expand All @@ -84,8 +86,8 @@ describe( 'Password', () => {
it( 'should display the weak password notice after submitting', () => {
// State: the user has updated their password to something weak
// although the strength is not tested here.
mockContext.userRecord.editedRecord.password = 'weak';
mockContext.userRecord.hasEdits = true;
mockContext.user.userRecord.editedRecord.password = 'weak';
mockContext.user.userRecord.hasEdits = true;

useContext.mockReturnValue( mockContext );

Expand All @@ -112,8 +114,8 @@ describe( 'Password', () => {
it( 'should display the strong password notice', () => {
// State: the user has updated their password to something strong
// although the strength is not tested here.
mockContext.userRecord.editedRecord.password = '@#4asdf34asdfasdf';
mockContext.userRecord.hasEdits = true;
mockContext.user.userRecord.editedRecord.password = '@#4asdf34asdfasdf';
mockContext.user.userRecord.hasEdits = true;

useContext.mockReturnValue( mockContext );

Expand Down Expand Up @@ -145,16 +147,16 @@ describe( 'Password', () => {
const input = getByLabelText( 'New Password' );
const newPassword = 'this is a password';
fireEvent.change( input, { target: { value: newPassword } } );
expect( mockContext.userRecord.edit ).toHaveBeenCalledWith( {
expect( mockContext.user.userRecord.edit ).toHaveBeenCalledWith( {
password: newPassword,
} );
} );

it( 'should submit form on button press', () => {
// State: the user has updated their password to something strong
// although the strength is not tested here.
mockContext.userRecord.editedRecord.password = '@#4asdf34asdfasdf';
mockContext.userRecord.hasEdits = true;
mockContext.user.userRecord.editedRecord.password = '@#4asdf34asdfasdf';
mockContext.user.userRecord.hasEdits = true;

useContext.mockReturnValue( mockContext );

Expand All @@ -171,7 +173,7 @@ describe( 'Password', () => {
const saveButton = buttons.filter( ( button ) => button.type === 'submit' )[ 0 ];
fireEvent.click( saveButton );

expect( mockContext.userRecord.save ).toHaveBeenCalled();
expect( mockContext.user.userRecord.save ).toHaveBeenCalled();
} );

it( 'should submit form on enter', () => {
Expand All @@ -186,6 +188,6 @@ describe( 'Password', () => {
const input = getByLabelText( 'New Password' );
fireEvent.submit( input );

expect( mockContext.userRecord.save ).toHaveBeenCalled();
expect( mockContext.user.userRecord.save ).toHaveBeenCalled();
} );
} );
50 changes: 25 additions & 25 deletions settings/src/components/tests/utlitites.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { useEntityRecord } from '@wordpress/core-data';
/**
* Local dependencies
*/
import { useGetUserRecord, refreshRecord } from '../../utilities';
import { useUser, refreshRecord } from '../../utilities';

jest.mock( '@wordpress/data' );
jest.mock( '@wordpress/core-data' );

describe( 'useGetUserRecord', () => {
describe( 'useUser', () => {
beforeEach( () => {
useSelect.mockClear();
useEntityRecord.mockClear();
Expand All @@ -23,81 +23,81 @@ describe( 'useGetUserRecord', () => {
it( 'should call useEntityRecord with correct arguments', () => {
useEntityRecord.mockReturnValue( { record: {} } );

useGetUserRecord( 1 );
useUser( 1 );

expect( useEntityRecord ).toHaveBeenCalledWith( 'root', 'user', 1 );
} );

it( 'should set isSaving in userRecord', () => {
it( 'should set isSaving in user', () => {
useEntityRecord.mockReturnValue( { record: {} } );
useSelect.mockReturnValue( true );

const result = useGetUserRecord( 1 );
const user = useUser( 1 );

expect( useSelect ).toHaveBeenCalled();
expect( result.isSaving ).toBeTruthy();
expect( user.isSaving ).toBeTruthy();
} );

it( 'should initialize password in userRecord.record if not defined', () => {
it( 'should initialize password in user.userRecord.record if not defined', () => {
useEntityRecord.mockReturnValue( { record: {} } );

const result = useGetUserRecord( 1 );
const user = useUser( 1 );

expect( result.record.password ).toBe( '' );
expect( user.userRecord.record.password ).toBe( '' );
} );

it( 'should not overwrite password in userRecord.record if already defined', () => {
it( 'should not overwrite password in user.userRecord.record if already defined', () => {
useEntityRecord.mockReturnValue( {
record: { password: 'test-password' },
} );

const result = useGetUserRecord( 1 );
const user = useUser( 1 );

expect( result.record.password ).toBe( 'test-password' );
expect( user.userRecord.record.password ).toBe( 'test-password' );
} );

it( 'should set hasPrimaryProvider to false if userRecord.record is undefined', () => {
it( 'should set hasPrimaryProvider to false if user.userRecord.record is undefined', () => {
useEntityRecord.mockReturnValue( {} );

const result = useGetUserRecord( 1 );
const user = useUser( 1 );

expect( result.hasPrimaryProvider ).toBe( false );
expect( user.hasPrimaryProvider ).toBe( false );
} );

it( 'should set hasPrimaryProvider to false if 2fa_available_providers is undefined', () => {
useEntityRecord.mockReturnValue( { record: {} } );

const result = useGetUserRecord( 1 );
const user = useUser( 1 );

expect( result.hasPrimaryProvider ).toBe( false );
expect( user.hasPrimaryProvider ).toBe( false );
} );

it( 'should set hasPrimaryProvider to false if 2fa_available_providers is empty', () => {
useEntityRecord.mockReturnValue( { record: { '2fa_available_providers': [] } } );

const result = useGetUserRecord( 1 );
const user = useUser( 1 );

expect( result.hasPrimaryProvider ).toBe( false );
expect( user.hasPrimaryProvider ).toBe( false );
} );

it( 'should set hasPrimaryProvider to false if 2fa_available_providers only has Two_Factor_Backup_Codes', () => {
useEntityRecord.mockReturnValue( {
record: { '2fa_available_providers': [ 'Two_Factor_Backup_Codes' ] },
} );

const result = useGetUserRecord( 1 );
const user = useUser( 1 );

expect( result.hasPrimaryProvider ).toBe( false );
expect( user.hasPrimaryProvider ).toBe( false );
} );

it( 'should set hasPrimaryProvider to true if 2fa_available_providers has Two_Factor_Totp', () => {
useEntityRecord.mockReturnValue( {
record: { '2fa_available_providers': [ 'Two_Factor_Totp', 'Two_Factor_Backup_Codes' ] },
} );

const result = useGetUserRecord( 1 );
const user = useUser( 1 );

expect( result.hasPrimaryProvider ).toBe( true );
expect( user.hasPrimaryProvider ).toBe( true );
} );

it( 'should set hasPrimaryProvider to true if 2fa_available_providers has TwoFactor_Provider_WebAuthn', () => {
Expand All @@ -110,9 +110,9 @@ describe( 'useGetUserRecord', () => {
},
} );

const result = useGetUserRecord( 1 );
const user = useUser( 1 );

expect( result.hasPrimaryProvider ).toBe( true );
expect( user.hasPrimaryProvider ).toBe( true );
} );
} );

Expand Down
15 changes: 12 additions & 3 deletions settings/src/components/totp.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { refreshRecord } from '../utilities';
import { GlobalContext } from '../script';

export default function TOTP() {
const { userRecord } = useContext( GlobalContext );
const {
user: { userRecord },
} = useContext( GlobalContext );
const availableProviders = userRecord.record[ '2fa_available_providers' ];
const totpEnabled = availableProviders.includes( 'Two_Factor_Totp' );

Expand All @@ -30,7 +32,11 @@ export default function TOTP() {
* Setup the TOTP provider.
*/
function Setup() {
const { clickScreenLink, setGlobalNotice, userRecord } = useContext( GlobalContext );
const {
clickScreenLink,
setGlobalNotice,
user: { userRecord },
} = useContext( GlobalContext );
const [ secretKey, setSecretKey ] = useState( '' );
const [ qrCodeUrl, setQrCodeUrl ] = useState( '' );
const [ error, setError ] = useState( '' );
Expand Down Expand Up @@ -278,7 +284,10 @@ function SetupForm( { handleEnable, qrCodeUrl, secretKey, inputs, setInputs, err
* Disable the TOTP provider.
*/
function Manage() {
const { userRecord, setGlobalNotice } = useContext( GlobalContext );
const {
user: { userRecord },
setGlobalNotice,
} = useContext( GlobalContext );
const [ error, setError ] = useState( '' );

// Enable TOTP when button clicked.
Expand Down
Loading

0 comments on commit 8f430ee

Please sign in to comment.