diff --git a/.gitignore b/.gitignore index 696dc0586a..015f87578c 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ packages/fxa-react/**/*.d.ts # fxa-settings packages/fxa-settings/fxa-content-server-l10n/ packages/fxa-settings/public/locales +packages/fxa-settings/test/ diff --git a/packages/fxa-react/lib/test-utils/index.test.tsx b/packages/fxa-react/lib/test-utils/index.test.tsx new file mode 100644 index 0000000000..6c3e574171 --- /dev/null +++ b/packages/fxa-react/lib/test-utils/index.test.tsx @@ -0,0 +1,151 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { FtlMsg, FtlMsgProps } from '../utils'; +import { getFtlBundle, testL10n } from '.'; +import { FluentBundle } from '@fluent/bundle'; + +jest.mock('fxa-react/lib/utils', () => ({ + FtlMsg: (props: FtlMsgProps) => ( +
+ {props.children} +
+ ), +})); + +const simpleComponent = (id: string, fallbackText = '') => ( + {fallbackText} +); + +const componentWithAttrs = (id: string, fallbackText = '') => ( + +

{fallbackText}

+
+); + +const componentWithVar = (fallbackText: string, name: string) => ( + +

{fallbackText}

+
+); + +describe('testL10n', () => { + let bundle: FluentBundle; + beforeAll(async () => { + bundle = await getFtlBundle(null); + }); + + it('throws if FTL ID is not found', () => { + expect(() => { + render(simpleComponent('not-in-bundle')); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).toThrowError( + 'Could not retrieve Fluent message tied to ID: not-in-bundle' + ); + }); + + it('throws if FTL ID is present, but message is not found', () => { + expect(() => { + render(simpleComponent('test-missing-message')); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).toThrowError( + 'Could not retrieve Fluent message tied to ID: test-missing-message' + ); + }); + + it('throws if FTL ID and attributes are present, but message is not found', () => { + expect(() => { + render(componentWithAttrs('test-missing-message-attrs')); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).toThrowError( + 'Could not retrieve Fluent message tied to ID: test-missing-message-attrs' + ); + }); + + it('successfully tests simple messages', () => { + expect(() => { + render(simpleComponent('test-simple', 'Simple and clean')); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).not.toThrow(); + }); + + it('successfully tests messages containing attributes', () => { + expect(() => { + render(componentWithAttrs('test-attrs', 'When you walk away')); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).not.toThrow(); + }); + + it('successfully tests messages with terms', () => { + expect(() => { + render(simpleComponent('test-term', 'Lately, Mozilla is all I need')); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).not.toThrow(); + }); + + describe('testMessage', () => { + it('throws if FTL message does not match fallback text', () => { + expect(() => { + render(simpleComponent('test-simple', 'testing 123')); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).toThrowError('Fallback text does not match Fluent message'); + }); + + it('throws if FTL message contains straight apostrophe', () => { + expect(() => { + render( + simpleComponent('test-straight-apostrophe', "you don't hear me say") + ); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).toThrowError('Fluent message contains a straight apostrophe'); + }); + + it('throws if FTL message contains straight quote', () => { + expect(() => { + render(simpleComponent('test-straight-quote', '"please, don’t go"')); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).toThrowError('Fluent message contains a straight quote'); + }); + + it('throws if FTL message expects variable not provided', () => { + const name = 'Sora'; + expect(() => { + render(componentWithVar(`${name} smiled at me`, name)); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle); + }).toThrowError('Unknown variable: $name'); + }); + + it('successfully tests messages with variables', () => { + const name = 'Sora'; + expect(() => { + render(componentWithVar(`${name} smiled at me`, name)); + + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle, { name }); + }).not.toThrow(); + }); + }); +}); diff --git a/packages/fxa-react/lib/test-utils/index.ts b/packages/fxa-react/lib/test-utils/index.ts new file mode 100644 index 0000000000..66971f40f1 --- /dev/null +++ b/packages/fxa-react/lib/test-utils/index.ts @@ -0,0 +1,164 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import path from 'path'; +import fs from 'fs'; +import { FluentBundle, FluentResource, FluentVariable } from '@fluent/bundle'; +import { Pattern } from '@fluent/bundle/esm/ast'; +import { queries, Screen } from '@testing-library/react'; + +type PackageName = 'settings' | 'payments' | null; + +// Testing locales other than the default will load bundles from the l10n repo. +async function getFtlFromPackage(packageName: PackageName, locale: string) { + let ftlPath: string; + + switch (packageName) { + case 'settings': + if (locale === 'en') { + ftlPath = path.join( + __dirname, + '..', + '..', + '..', + 'fxa-settings', + 'test', + 'settings.ftl' + ); + } else { + // TODO: Not currently used, but probably want to add one translation test + ftlPath = path.join( + __dirname, + '..', + '..', + '..', + 'fxa-settings', + 'public', + 'locales', + locale, + 'settings.ftl' + ); + } + break; + case 'payments': + // TODO: Not currently used. We need to set up test stuff for payments similarly, FXA-5996 + ftlPath = path.join( + __dirname, + '..', + '..', + '..', + 'fxa-payments-server', + 'public', + 'locales', + locale, + 'main.ftl' + ); + break; + default: + ftlPath = path.join(__dirname, 'test.ftl'); + break; + } + return fs.promises.readFile(ftlPath, 'utf8'); +} + +/** + * Get the specified FTL file/bundle. + * @packageName Which package to load the bundle for + * @locale Which locale FTL bundle to load. 'en' will load `test/[name].ftl` and other + * locales pull from the cloned l10n repo in `public`. + */ +export async function getFtlBundle(packageName: PackageName, locale = 'en') { + const messages = await getFtlFromPackage(packageName, locale); + const resource = new FluentResource(messages); + const bundle = new FluentBundle(locale, { useIsolating: false }); + bundle.addResource(resource); + return bundle; +} + +function testMessage( + bundle: FluentBundle, + pattern: Pattern, + fallbackText: string | null, + ftlArgs?: Record +) { + const ftlMsg = bundle.formatPattern(pattern, ftlArgs); + + // We allow for .includes because fallback text comes from `textContent` within the + // `FtlMsg` wrapper which may contain more than one component and string + if (!fallbackText?.includes(ftlMsg)) { + throw Error( + `Fallback text does not match Fluent message.\nFallback text: ${fallbackText}\nFluent message: ${ftlMsg}` + ); + } + + if (ftlMsg.includes("'")) { + throw Error( + `Fluent message contains a straight apostrophe (') and must be updated to its curly equivalent (’). Fluent message: ${ftlMsg}` + ); + } + + if (ftlMsg.includes('"')) { + throw Error( + `Fluent message contains a straight quote (") and must be updated to its curly equivalent (“”). Fluent message: ${ftlMsg}` + ); + } +} + +/** + * Convenience function for running `testL10n` against all mocked `FtlMsg`s + * (`data-testid='ftlmsg-mock'`) found. + * @param screen + * @param bundle Fluent bundle created during test setup + */ +export function testAllL10n( + { getAllByTestId }: Screen, + bundle: FluentBundle +) { + const ftlMsgMocks = getAllByTestId('ftlmsg-mock'); + ftlMsgMocks.forEach((ftlMsgMock) => { + testL10n(ftlMsgMock, bundle); + }); +} + +/** + * Takes in a mocked FtlMsg and tests that: + * * Fluent IDs and message are present in the Fluent bundle + * * Fluent messages match fallback text + * * Fluent messages don't contain any straight apostrophes or quotes + * * Variables are provided + * @param ftlMsgMock Mocked version of `FtlMsg` (`data-testid='ftlmsg-mock'`) + * @param bundle Fluent bundle created during test setup + * @param ftlArgs Optional Fluent variables to be passed into the message + */ +export function testL10n( + ftlMsgMock: HTMLElement, + bundle: FluentBundle, + ftlArgs?: Record +) { + const ftlId = ftlMsgMock.getAttribute('id')!; + const fallbackText = ftlMsgMock.textContent; + const ftlBundleMsg = bundle.getMessage(ftlId); + + // nested attributes can happen when we define something like: + // `profile-picture = + // .header = Picture` + const nestedAttrValues = Object.values(ftlBundleMsg?.attributes || {}); + + if ( + ftlBundleMsg === undefined || + (ftlBundleMsg.value === null && nestedAttrValues.length === 0) + ) { + throw Error(`Could not retrieve Fluent message tied to ID: ${ftlId}`); + } + + if (ftlBundleMsg.value) { + testMessage(bundle, ftlBundleMsg.value, fallbackText, ftlArgs); + } + + if (nestedAttrValues) { + nestedAttrValues.forEach((nestedAttrValue) => + testMessage(bundle, nestedAttrValue, fallbackText, ftlArgs) + ); + } +} diff --git a/packages/fxa-react/lib/test-utils/test.ftl b/packages/fxa-react/lib/test-utils/test.ftl new file mode 100644 index 0000000000..91b7adb364 --- /dev/null +++ b/packages/fxa-react/lib/test-utils/test.ftl @@ -0,0 +1,11 @@ +-term = Mozilla +test-simple = Simple and clean +test-missing-message = +test-missing-message-attrs = + .header = +test-attrs = + .header = When you walk away +test-straight-apostrophe = you don't hear me say +test-straight-quote = "please, don’t go" +test-var = { $name } smiled at me +test-term = Lately, { -term } is all I need diff --git a/packages/fxa-react/lib/utils.tsx b/packages/fxa-react/lib/utils.tsx new file mode 100644 index 0000000000..1c4e1b93c4 --- /dev/null +++ b/packages/fxa-react/lib/utils.tsx @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from 'react'; +import { Localized, LocalizedProps } from '@fluent/react'; + +export type FtlMsgProps = { + children: React.ReactNode; +} & LocalizedProps; + +export const FtlMsg = (props: FtlMsgProps) => ( + {props.children} +); diff --git a/packages/fxa-settings/.rescriptsrc.js b/packages/fxa-settings/.rescriptsrc.js index 0781b9e4ff..a3fba49844 100644 --- a/packages/fxa-settings/.rescriptsrc.js +++ b/packages/fxa-settings/.rescriptsrc.js @@ -4,20 +4,8 @@ const path = require('path'); const fs = require('fs'); -const { default: WebpackWatchPlugin } = require('webpack-watch-files-plugin'); -const MergeIntoSingleFilePlugin = require('webpack-merge-and-include-globally'); const { permitAdditionalJSImports } = require('fxa-react/configs/rescripts'); -const watchFtlPlugin = new WebpackWatchPlugin({ - files: ['src/**/*.ftl'], -}); - -const mergeFtlPlugin = new MergeIntoSingleFilePlugin({ - files: { - '../public/locales/en-US/settings.ftl': ['.license.header', 'src/**/*.ftl'], - }, -}); - module.exports = [ { devServer: (config) => { @@ -45,11 +33,6 @@ module.exports = [ }; } - newConfig = { - ...newConfig, - plugins: [...newConfig.plugins, watchFtlPlugin, mergeFtlPlugin], - }; - return newConfig; }, }, diff --git a/packages/fxa-settings/Gruntfile.js b/packages/fxa-settings/Gruntfile.js new file mode 100644 index 0000000000..73551c716d --- /dev/null +++ b/packages/fxa-settings/Gruntfile.js @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +'use strict'; + +module.exports = function (grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + concat: { + ftl: { + src: ['.license.header', 'src/**/*.ftl'], + // TODO: change dest to `en` in FXA-6003 + dest: 'public/locales/en-US/settings.ftl', + }, + + // We need this for tests because we pull the latest from `fxa-content-server-l10n` + // and place those in our `public` directory at `postinstall` time, and sometimes we have + // FTL updates on our side that haven't landed yet on the l10n side. We want to test + // against _our_ latest, and not necessarily the l10n repo's latest. + 'ftl-test': { + src: ['.license.header', 'src/**/*.ftl'], + dest: 'test/settings.ftl', + }, + }, + watch: { + ftl: { + files: ['src/**/*.ftl'], + tasks: ['merge-ftl'], + options: { + interrupt: true, + }, + }, + }, + }); + + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-concat'); + + grunt.registerTask('merge-ftl', ['concat:ftl']); + grunt.registerTask('merge-ftl:test', ['concat:ftl-test']); + grunt.registerTask('watch-ftl', ['watch:ftl']); +}; diff --git a/packages/fxa-settings/package.json b/packages/fxa-settings/package.json index 210a2d9179..27af587739 100644 --- a/packages/fxa-settings/package.json +++ b/packages/fxa-settings/package.json @@ -4,21 +4,24 @@ "homepage": "https://accounts.firefox.com/settings", "private": true, "scripts": { - "postinstall": "../../_scripts/clone-l10n.sh fxa-settings", + "postinstall": "grunt merge-ftl &&../../_scripts/clone-l10n.sh fxa-settings", "build-css": "tailwindcss -i ./src/styles/tailwind.css -o ./src/styles/tailwind.out.css --postcss", "build-storybook": "NODE_ENV=production STORYBOOK_BUILD=1 npm run build-css && build-storybook", - "build": "tsc --build ../fxa-react && NODE_ENV=production npm run build-css && SKIP_PREFLIGHT_CHECK=true INLINE_RUNTIME_CHUNK=false rescripts build", + "build": "tsc --build ../fxa-react && NODE_ENV=production npm run build-css && npm run merge-ftl && SKIP_PREFLIGHT_CHECK=true INLINE_RUNTIME_CHUNK=false rescripts build", "eject": "react-scripts eject", "lint:eslint": "eslint . .storybook", "lint": "npm-run-all --parallel lint:eslint", "restart": "npm run build-css && pm2 restart pm2.config.js", - "start": "npm run build-css && pm2 start pm2.config.js && ../../_scripts/check-url.sh localhost:3000/settings/static/js/bundle.js", + "start": "npm run build-css && grunt merge-ftl && pm2 start pm2.config.js && ../../_scripts/check-url.sh localhost:3000/settings/static/js/bundle.js", "stop": "pm2 stop pm2.config.js", "delete": "pm2 delete pm2.config.js", "storybook": "STORYBOOK_BUILD=1 npm run build-css && start-storybook -p 6008 --no-version-updates", - "test": "SKIP_PREFLIGHT_CHECK=true rescripts test --watchAll=false", + "test": "yarn merge-ftl:test && SKIP_PREFLIGHT_CHECK=true rescripts test --watchAll=false", "test:watch": "SKIP_PREFLIGHT_CHECK=true rescripts test", - "test:coverage": "yarn test --coverage --watchAll=false" + "test:coverage": "yarn test --coverage --watchAll=false", + "merge-ftl": "grunt merge-ftl", + "merge-ftl:test": "grunt merge-ftl:test", + "watch-ftl": "grunt watch-ftl" }, "jest": { "resetMocks": false, @@ -115,6 +118,10 @@ "eslint-plugin-jest": "^24.5.2", "eslint-plugin-react": "^7.31.10", "fxa-shared": "workspace:*", + "grunt": "^1.5.3", + "grunt-cli": "^1.4.3", + "grunt-contrib-concat": "^2.1.0", + "grunt-contrib-watch": "^1.1.0", "jest-watch-typeahead": "0.6.5", "mutationobserver-shim": "^0.3.7", "npm-run-all": "^4.1.5", @@ -125,8 +132,6 @@ "storybook-addon-rtl": "^0.4.3", "style-loader": "^1.3.0", "tailwindcss": "^3.2.0", - "webpack": "^4.43.0", - "webpack-merge-and-include-globally": "^2.3.4", - "webpack-watch-files-plugin": "^1.2.1" + "webpack": "^4.43.0" } } diff --git a/packages/fxa-settings/pm2.config.js b/packages/fxa-settings/pm2.config.js index 83aa592dc8..23f6f5de79 100644 --- a/packages/fxa-settings/pm2.config.js +++ b/packages/fxa-settings/pm2.config.js @@ -53,5 +53,14 @@ module.exports = { ignore_watch: ['src/styles/tailwind.out.*'], time: true, }, + { + name: 'settings-ftl', + script: 'yarn grunt watch-ftl', + cwd: __dirname, + filter_env: ['npm_'], + max_restarts: '1', + min_uptime: '2m', + time: true, + }, ], }; diff --git a/packages/fxa-settings/src/components/BentoMenu/index.test.tsx b/packages/fxa-settings/src/components/BentoMenu/index.test.tsx index f978eed531..6dd00ee7f4 100644 --- a/packages/fxa-settings/src/components/BentoMenu/index.test.tsx +++ b/packages/fxa-settings/src/components/BentoMenu/index.test.tsx @@ -5,8 +5,14 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import BentoMenu from '.'; +import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils'; +import { FluentBundle } from '@fluent/bundle'; describe('BentoMenu', () => { + let bundle: FluentBundle; + beforeAll(async () => { + bundle = await getFtlBundle('settings'); + }); const dropDownId = 'drop-down-bento-menu'; it('renders and toggles as expected with default values', () => { @@ -23,6 +29,7 @@ describe('BentoMenu', () => { fireEvent.click(toggleButton); expect(toggleButton).toHaveAttribute('aria-expanded', 'true'); expect(screen.queryByTestId(dropDownId)).toBeInTheDocument(); + testAllL10n(screen, bundle); fireEvent.click(toggleButton); expect(toggleButton).toHaveAttribute('aria-expanded', 'false'); diff --git a/packages/fxa-settings/src/components/BentoMenu/index.tsx b/packages/fxa-settings/src/components/BentoMenu/index.tsx index ea2663b101..0ea07c2a32 100644 --- a/packages/fxa-settings/src/components/BentoMenu/index.tsx +++ b/packages/fxa-settings/src/components/BentoMenu/index.tsx @@ -17,6 +17,7 @@ import vpnIcon from './vpn-logo.svg'; import { ReactComponent as BentoIcon } from './bento.svg'; import { ReactComponent as CloseIcon } from 'fxa-react/images/close.svg'; import { Localized, useLocalization } from '@fluent/react'; +import { FtlMsg } from 'fxa-react/lib/utils'; export const BentoMenu = () => { const [isRevealed, setRevealed] = useState(false); @@ -154,7 +155,7 @@ export const BentoMenu = () => { - + { > Made by Mozilla - + diff --git a/packages/fxa-settings/src/components/Profile/index.test.tsx b/packages/fxa-settings/src/components/Profile/index.test.tsx index a81a0ab4bb..1ae544485b 100644 --- a/packages/fxa-settings/src/components/Profile/index.test.tsx +++ b/packages/fxa-settings/src/components/Profile/index.test.tsx @@ -6,28 +6,30 @@ import React from 'react'; import '@testing-library/jest-dom/extend-expect'; import { Profile } from '.'; import { mockAppContext, renderWithRouter } from '../../models/mocks'; -import { Account, AppContext } from '../../models'; +import { AppContext } from '../../models'; +import { MOCK_PROFILE_EMPTY } from './mocks'; +import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils'; +import { screen } from '@testing-library/react'; +import { FluentBundle } from '@fluent/bundle'; -const account = { - avatar: { url: null, id: null }, - primaryEmail: { - email: 'vladikoff@mozilla.com', - }, - emails: [], - displayName: 'Vlad', -} as unknown as Account; - -// todo: -// add test cases for different states, including secondary email describe('Profile', () => { + let bundle: FluentBundle; + beforeAll(async () => { + bundle = await getFtlBundle('settings'); + }); + it('renders "fresh load" with correct content', async () => { - const { findByText } = renderWithRouter( - + renderWithRouter( + ); + testAllL10n(screen, bundle); - expect(await findByText('Vlad')).toBeTruthy(); - expect(await findByText('vladikoff@mozilla.com')).toBeTruthy(); + await screen.findByAltText('Default avatar'); + expect(await screen.findAllByText('None')).toHaveLength(2); + await screen.findByText('johndope@example.com'); }); }); diff --git a/packages/fxa-settings/src/components/Profile/index.tsx b/packages/fxa-settings/src/components/Profile/index.tsx index 84d57605e4..e699f4c67c 100644 --- a/packages/fxa-settings/src/components/Profile/index.tsx +++ b/packages/fxa-settings/src/components/Profile/index.tsx @@ -1,9 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + import React from 'react'; import { useAccount } from '../../models'; import { UnitRow } from '../UnitRow'; import { UnitRowSecondaryEmail } from '../UnitRowSecondaryEmail'; import { HomePath } from '../../constants'; -import { Localized } from '@fluent/react'; +import { FtlMsg } from 'fxa-react/lib/utils'; export const Profile = () => { const { avatar, primaryEmail, displayName } = useAccount(); @@ -12,11 +16,11 @@ export const Profile = () => {

- Profile + Profile

- + { prefixDataTestId="avatar" {...{ avatar }} /> - +
- + { route="/settings/display_name" prefixDataTestId="display-name" /> - +
- + { headerValueClassName="break-all" prefixDataTestId="primary-email" /> - +
diff --git a/packages/fxa-settings/src/components/Security/index.test.tsx b/packages/fxa-settings/src/components/Security/index.test.tsx index 2921d4b74f..9896bddb15 100644 --- a/packages/fxa-settings/src/components/Security/index.test.tsx +++ b/packages/fxa-settings/src/components/Security/index.test.tsx @@ -7,8 +7,15 @@ import { screen } from '@testing-library/react'; import Security from '.'; import { mockAppContext, renderWithRouter } from '../../models/mocks'; import { Account, AppContext } from '../../models'; +import { getFtlBundle, testL10n } from 'fxa-react/lib/test-utils'; +import { FluentBundle } from '@fluent/bundle'; describe('Security', () => { + let bundle: FluentBundle; + beforeAll(async () => { + bundle = await getFtlBundle('settings'); + }); + it('renders "fresh load" with correct content', async () => { const account = { avatar: { url: null, id: null }, @@ -68,7 +75,7 @@ describe('Security', () => { passwordCreated: 1234567890, hasPassword: true, } as unknown as Account; - const createDate = new Date(1234567890).getDate(); + const createDate = `1/${new Date(1234567890).getDate()}/1970`; renderWithRouter( @@ -76,8 +83,13 @@ describe('Security', () => { ); const passwordRouteLink = screen.getByTestId('password-unit-row-route'); + const ftlMsgMock = screen.getByTestId('ftlmsg-mock'); + testL10n(ftlMsgMock, bundle, { + date: createDate, + }); + await screen.findByText('••••••••••••••••••'); - await screen.findByText(`Created 1/${createDate}/1970`); + await screen.findByText(`Created ${createDate}`); expect(passwordRouteLink).toHaveTextContent('Change'); expect(passwordRouteLink).toHaveAttribute( diff --git a/packages/fxa-settings/src/components/Security/index.tsx b/packages/fxa-settings/src/components/Security/index.tsx index 8e3048c4bf..76da136423 100644 --- a/packages/fxa-settings/src/components/Security/index.tsx +++ b/packages/fxa-settings/src/components/Security/index.tsx @@ -8,6 +8,7 @@ import UnitRowRecoveryKey from '../UnitRowRecoveryKey'; import UnitRowTwoStepAuth from '../UnitRowTwoStepAuth'; import { UnitRow } from '../UnitRow'; import { useAccount } from '../../models'; +import { FtlMsg } from 'fxa-react/lib/utils'; const PwdDate = ({ passwordCreated }: { passwordCreated: number }) => { const pwdDateText = Intl.DateTimeFormat('default', { @@ -17,11 +18,11 @@ const PwdDate = ({ passwordCreated }: { passwordCreated: number }) => { }).format(new Date(passwordCreated)); return ( - +

Created {pwdDateText}

-
+ ); }; @@ -62,8 +63,8 @@ export const Security = () => { ) : (

- Set a password to sync and use certain account - security features. + Set a password to sync and use certain account security + features.

)} diff --git a/packages/fxa-settings/src/setupTests.js b/packages/fxa-settings/src/setupTests.tsx similarity index 53% rename from packages/fxa-settings/src/setupTests.js rename to packages/fxa-settings/src/setupTests.tsx index 8c81f88ecb..d2e50f24f2 100644 --- a/packages/fxa-settings/src/setupTests.js +++ b/packages/fxa-settings/src/setupTests.tsx @@ -3,3 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import '@testing-library/jest-dom/extend-expect'; +import { FtlMsgProps } from 'fxa-react/lib/utils'; + +jest.mock('fxa-react/lib/utils', () => ({ + FtlMsg: (props: FtlMsgProps) => ( +
+ {props.children} +
+ ), +})); diff --git a/yarn.lock b/yarn.lock index 8ee1249dd6..4f91e5cb8b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21461,13 +21461,6 @@ __metadata: languageName: node linkType: hard -"es6-promisify@npm:^6.1.1": - version: 6.1.1 - resolution: "es6-promisify@npm:6.1.1" - checksum: e57dfa8b6533387e6cae115bdc1591e4e6e7648443741360c4f4f8f1d2c17d1f0fb293ccd3f86193f016c236ed15f336e075784eab7ec9a67af0aed2b949dd7c - languageName: node - linkType: hard - "es6-shim@npm:^0.35.5": version: 0.35.5 resolution: "es6-shim@npm:0.35.5" @@ -25361,6 +25354,10 @@ fsevents@~2.1.1: fxa-shared: "workspace:*" get-orientation: ^1.1.2 graphql: ^15.6.1 + grunt: ^1.5.3 + grunt-cli: ^1.4.3 + grunt-contrib-concat: ^2.1.0 + grunt-contrib-watch: ^1.1.0 jest-watch-typeahead: 0.6.5 lodash.groupby: ^4.6.0 mutationobserver-shim: ^0.3.7 @@ -25383,8 +25380,6 @@ fsevents@~2.1.1: typescript: ^4.8.2 uuid: ^9.0.0 webpack: ^4.43.0 - webpack-merge-and-include-globally: ^2.3.4 - webpack-watch-files-plugin: ^1.2.1 languageName: unknown linkType: soft @@ -41353,13 +41348,6 @@ resolve@^2.0.0-next.3: languageName: node linkType: hard -"rev-hash@npm:^3.0.0": - version: 3.0.0 - resolution: "rev-hash@npm:3.0.0" - checksum: d7af9e411b945cae85c40d3d5e061589b5e84b16d3ffddcee948766ca4e70e7040942fc9aee0e8a4d64e1edf832730687bbcaf63577b8b6ca59866116bd8f6bd - languageName: node - linkType: hard - "rework-visit@npm:1.0.0": version: 1.0.0 resolution: "rework-visit@npm:1.0.0" @@ -47182,19 +47170,6 @@ resolve@^2.0.0-next.3: languageName: node linkType: hard -"webpack-merge-and-include-globally@npm:^2.3.4": - version: 2.3.4 - resolution: "webpack-merge-and-include-globally@npm:2.3.4" - dependencies: - es6-promisify: ^6.1.1 - glob: ^7.1.6 - rev-hash: ^3.0.0 - peerDependencies: - webpack: ">=1.0.0" - checksum: 00e99409653303ef9e99ba9f622a35f890110932d6feecaa65779f2bf8237a892ac5905bb4a4ea4fd41dd2e584461ee9de4f366e7ce307737e9c0e5b9693fe39 - languageName: node - linkType: hard - "webpack-merge@npm:^5.7.3": version: 5.7.3 resolution: "webpack-merge@npm:5.7.3"