Skip to content

Commit

Permalink
[#38] Add a small language switcher component
Browse files Browse the repository at this point in the history
  • Loading branch information
malparty committed Oct 25, 2021
1 parent 7aefeca commit 713b0a6
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 0 deletions.
4 changes: 4 additions & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"error_title": "Error",
"error_icon_alt": "Error sign icon"
},
"language_switch": {
"en": "English",
"fr": "French"
},
"login": {
"title": "Sign in to Nimble",
"email": "Email",
Expand Down
24 changes: 24 additions & 0 deletions public/locales/fr/translation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"base_alert": {
"error_title": "Erreur",
"error_icon_alt": "Icone indiquant une erreur"
},
"language_switch": {
"en": "Anglais",
"fr": "Français"
},
"login": {
"title": "Se connecter à Nimble",
"email": "Email",
"password": "Mot de passe",
"submit": "Se connecter",
"errors": {
"email_required": "Veuillez saisir votre email.",
"email_invalid": "L'email saisi n'est pas valid.",
"password_required": "Veuillez saisir votre mot de passe.",
"password_too_long": "Le mot de passe ne doit pas dépasser les 32 caractères.",
"password_too_short": "Le mot de passe doit faire au minimum 8 caractères."
},
"nimble_logo_alt": "Logo de Nimble"
}
}
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Component } from 'react';
import './assets/stylesheets/application.scss';
import BackgroundImage from './components/BackgroundImage';
import LanguageSwitch from './components/LanguageSwitch';
import LoginScreen from './screens/Login';

export default class App extends Component {
render() {
return (
<div className="application">
<BackgroundImage />
<LanguageSwitch />
<LoginScreen />
</div>
);
Expand Down
30 changes: 30 additions & 0 deletions src/components/LanguageSwitch/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useTranslation } from 'react-i18next';

const LanguageSwitch = () => {
const { t, i18n } = useTranslation();

const changeLanguage = (language: string): void => {
i18n.changeLanguage(language);
};

return (
<ul className="language-switch">
{i18n.languages.map((language) => {
return (
<li key={language}>
<a
onClick={() => {
changeLanguage(language);
}}
data-lang={language}
>
{t(`language_switch.${language}`)}
</a>
</li>
);
})}
</ul>
);
};

export default LanguageSwitch;

1 comment on commit 713b0a6

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

❌ An unexpected error occurred. For more details, check console

Error: The process '/usr/local/bin/npx' failed with exit code 1
St.
Category Percentage Covered / Total
🟡 Statements 79.07% 34/43
🔴 Branches 33.33% 2/6
🟡 Functions 65% 13/20
🟢 Lines 82.93% 34/41

Test suite run failed

Failed tests: 17/27. Failed suites: 5/7.
  ● LoginForm › correctly renders

    expect(received).toMatchSnapshot()

    Snapshot name: `LoginForm correctly renders 1`

    - Snapshot  - 11
    + Received  + 11

    @@ -4,18 +4,18 @@
          <div>
            <div
              class="login-form"
            >
              <img
    -           alt="Nimble Logo"
    +           alt="login.nimble_logo_alt"
                class="login-form__header-image"
                src="nimble-logo.png"
              />
              <div
                class="login-form__header-title"
              >
    -           Sign in to Nimble
    +           login.title
              </div>
              <form
                action="#"
              >
                <div
    @@ -23,11 +23,11 @@
                >
                  <label
                    class="login-form__label"
                    for="email"
                  >
    -               Email
    +               login.email
                  </label>
                  <input
                    class="login-form__input form-control"
                    id="email"
                    name="email"
    @@ -40,11 +40,11 @@
                >
                  <label
                    class="login-form__label"
                    for="password"
                  >
    -               Password
    +               login.password
                  </label>
                  <input
                    class="login-form__input form-control"
                    id="password"
                    name="password"
    @@ -57,30 +57,30 @@
                >
                  <button
                    class="btn btn-light"
                    type="submit"
                  >
    -               Sign in
    +               login.submit
                  </button>
                </div>
              </form>
            </div>
          </div>
        </body>,
    -   "container": <div>
    +   "container": <div>
          <div
            class="login-form"
          >
            <img
    -         alt="Nimble Logo"
    +         alt="login.nimble_logo_alt"
              class="login-form__header-image"
              src="nimble-logo.png"
            />
            <div
              class="login-form__header-title"
            >
    -         Sign in to Nimble
    +         login.title
            </div>
            <form
              action="#"
            >
              <div
    @@ -88,11 +88,11 @@
              >
                <label
                  class="login-form__label"
                  for="email"
                >
    -             Email
    +             login.email
                </label>
                <input
                  class="login-form__input form-control"
                  id="email"
                  name="email"
    @@ -105,11 +105,11 @@
              >
                <label
                  class="login-form__label"
                  for="password"
                >
    -             Password
    +             login.password
                </label>
                <input
                  class="login-form__input form-control"
                  id="password"
                  name="password"
    @@ -122,11 +122,11 @@
              >
                <button
                  class="btn btn-light"
                  type="submit"
                >
    -             Sign in
    +             login.submit
                </button>
              </div>
            </form>
          </div>
        </div>,

       7 |     const loginForm = render(<LoginForm />);
       8 |
    >  9 |     expect(loginForm).toMatchSnapshot();
         |                       ^
      10 |   });
      11 |
      12 |   it('displays an email field with its label', () => {

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:9:23)

  ● LoginForm › displays an email field with its label

    TestingLibraryElementError: Unable to find a label with the text of: Email

    <body>
      <div>
        <div
          class="login-form"
        >
          <img
            alt="login.nimble_logo_alt"
            class="login-form__header-image"
            src="nimble-logo.png"
          />
          <div
            class="login-form__header-title"
          >
            login.title
          </div>
          <form
            action="#"
          >
            <div
              class="login-form__field"
            >
              <label
                class="login-form__label"
                for="email"
              >
                login.email
              </label>
              <input
                class="login-form__input form-control"
                id="email"
                name="email"
                type="email"
                value=""
              />
            </div>
            <div
              class="login-form__field"
            >
              <label
                class="login-form__label"
                for="password"
              >
                login.password
              </label>
              <input
                class="login-form__input form-control"
                id="password"
                name="password"
                type="password"
                value=""
              />
            </div>
            <div
              class="login-form__field"
            >
              <button
                class="btn btn-light"
                type="submit"
              >
                login.submit
              </button>
            </div>
          </form>
        </div>
      </div>
    </body>

      12 |   it('displays an email field with its label', () => {
      13 |     const loginForm = render(<LoginForm />);
    > 14 |     expect(loginForm.getByLabelText('Email')).toBeVisible();
         |                      ^
      15 |   });
      16 |
      17 |   it('displays a password field with its label', () => {

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
      at getAllByLabelText (node_modules/@testing-library/dom/dist/queries/label-text.js:119:38)
      at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
      at getByLabelText (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:14:22)

  ● LoginForm › displays a password field with its label

    TestingLibraryElementError: Unable to find a label with the text of: Password

    <body>
      <div>
        <div
          class="login-form"
        >
          <img
            alt="login.nimble_logo_alt"
            class="login-form__header-image"
            src="nimble-logo.png"
          />
          <div
            class="login-form__header-title"
          >
            login.title
          </div>
          <form
            action="#"
          >
            <div
              class="login-form__field"
            >
              <label
                class="login-form__label"
                for="email"
              >
                login.email
              </label>
              <input
                class="login-form__input form-control"
                id="email"
                name="email"
                type="email"
                value=""
              />
            </div>
            <div
              class="login-form__field"
            >
              <label
                class="login-form__label"
                for="password"
              >
                login.password
              </label>
              <input
                class="login-form__input form-control"
                id="password"
                name="password"
                type="password"
                value=""
              />
            </div>
            <div
              class="login-form__field"
            >
              <button
                class="btn btn-light"
                type="submit"
              >
                login.submit
              </button>
            </div>
          </form>
        </div>
      </div>
    </body>

      18 |     const loginForm = render(<LoginForm />);
      19 |
    > 20 |     expect(loginForm.getByLabelText('Password')).toBeVisible();
         |                      ^
      21 |   });
      22 |
      23 |   it('displays a Sign In button', () => {

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
      at getAllByLabelText (node_modules/@testing-library/dom/dist/queries/label-text.js:119:38)
      at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
      at getByLabelText (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:20:22)

  ● LoginForm › displays a Sign In button

    TestingLibraryElementError: Unable to find an element with the text: Sign in. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    <body>
      <div>
        <div
          class="login-form"
        >
          <img
            alt="login.nimble_logo_alt"
            class="login-form__header-image"
            src="nimble-logo.png"
          />
          <div
            class="login-form__header-title"
          >
            login.title
          </div>
          <form
            action="#"
          >
            <div
              class="login-form__field"
            >
              <label
                class="login-form__label"
                for="email"
              >
                login.email
              </label>
              <input
                class="login-form__input form-control"
                id="email"
                name="email"
                type="email"
                value=""
              />
            </div>
            <div
              class="login-form__field"
            >
              <label
                class="login-form__label"
                for="password"
              >
                login.password
              </label>
              <input
                class="login-form__input form-control"
                id="password"
                name="password"
                type="password"
                value=""
              />
            </div>
            <div
              class="login-form__field"
            >
              <button
                class="btn btn-light"
                type="submit"
              >
                login.submit
              </button>
            </div>
          </form>
        </div>
      </div>
    </body>

      24 |     const loginForm = render(<LoginForm />);
      25 |
    > 26 |     expect(loginForm.getByText('Sign in')).toBeVisible();
         |                      ^
      27 |   });
      28 |
      29 |   it('has 2 errors when providing 2 errors and 2 touched field', () => {

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
      at node_modules/@testing-library/dom/dist/query-helpers.js:90:38
      at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
      at getByText (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:26:22)

  ● LoginForm › has 2 errors when providing 2 errors and 2 touched field

    TypeError: _LoginForm.default is not a constructor

      28 |
      29 |   it('has 2 errors when providing 2 errors and 2 touched field', () => {
    > 30 |     const loginForm = new LoginForm({});
         |                       ^
      31 |     const errors = { email: 'Invalid', password: 'Invalid' };
      32 |     const touched = { email: true, password: true };
      33 |

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:30:23)

  ● LoginForm › has an error when providing 2 errors but only 1 touched field

    TypeError: _LoginForm.default is not a constructor

      36 |
      37 |   it('has an error when providing 2 errors but only 1 touched field', () => {
    > 38 |     const loginForm = new LoginForm({});
         |                       ^
      39 |     const errors = { email: 'Invalid', password: 'Invalid' };
      40 |     const touched = { password: true };
      41 |

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:38:23)

  ● LoginForm › has no error when providing 2 errors but no touched field

    TypeError: _LoginForm.default is not a constructor

      44 |
      45 |   it('has no error when providing 2 errors but no touched field', () => {
    > 46 |     const loginForm = new LoginForm({});
         |                       ^
      47 |     const errors = { email: 'Invalid', password: 'Invalid' };
      48 |     const touched = {};
      49 |

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:46:23)

  ● LoginForm › has no error when providing 0 errors but a touched field

    TypeError: _LoginForm.default is not a constructor

      52 |
      53 |   it('has no error when providing 0 errors but a touched field', () => {
    > 54 |     const loginForm = new LoginForm({});
         |                       ^
      55 |     const errors = {};
      56 |     const touched = { email: true };
      57 |

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:54:23)

  ● LoginForm › has no error when providing 0 errors but 2 touched field

    TypeError: _LoginForm.default is not a constructor

      60 |
      61 |   it('has no error when providing 0 errors but 2 touched field', () => {
    > 62 |     const loginForm = new LoginForm({});
         |                       ^
      63 |     const errors = {};
      64 |     const touched = { email: true, password: true };
      65 |

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:62:23)

  ● LoginForm › has no error when providing 0 errors and no touched field

    TypeError: _LoginForm.default is not a constructor

      68 |
      69 |   it('has no error when providing 0 errors and no touched field', () => {
    > 70 |     const loginForm = new LoginForm({});
         |                       ^
      71 |     const errors = {};
      72 |     const touched = {};
      73 |

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:70:23)

  ● LoginForm › calls the setSubmitting callback after submitLoginForn is called

    TypeError: _LoginForm.default is not a constructor

      76 |
      77 |   it('calls the setSubmitting callback after submitLoginForn is called', async () => {
    > 78 |     const loginForm = new LoginForm({});
         |                       ^
      79 |     const values = { email: 'test@email.com', password: '012345678' };
      80 |     const formikHelpers = { setSubmitting: jest.fn() } as unknown as FormikHelpers<LoginFormValues>;
      81 |

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:78:23)

  ● LoginForm › displays a BaseAlert message when it has an error

    TypeError: _LoginForm.default is not a constructor

      86 |
      87 |   it('displays a BaseAlert message when it has an error', () => {
    > 88 |     const loginForm = new LoginForm({});
         |                       ^
      89 |
      90 |     const formErrors = loginForm.renderErrors({email:'Email error'}, {email: true});
      91 |

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:88:23)

  ● LoginForm › does NOT display a BaseAlert message when it has no error

    TypeError: _LoginForm.default is not a constructor

      94 |
      95 |   it('does NOT display a BaseAlert message when it has no error', () => {
    > 96 |     const loginForm = new LoginForm({});
         |                       ^
      97 |
      98 |     const formErrors = loginForm.renderErrors({email:'Email error'}, {password: true});
      99 |

      at Object.<anonymous> (src/tests/components/LoginForm/index.test.tsx:96:23)


  ● renders with App and root div

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    - Expected
    + Received

      <UNDEFINED>
    +   <React.Suspense
    +     fallback="loading"
    +   >
          <App />
    +   </React.Suspense>
      </UNDEFINED>,
      <div id="root" />,

    Number of calls: 1

      20 |   // Asserts render was called with <App /> wrapped by <React.ScrictMode>
      21 |   // and HTML element with id = root
    > 22 |   expect(ReactDOM.render).toHaveBeenCalledWith(
         |                           ^
      23 |     <React.StrictMode>
      24 |       <App />
      25 |     </React.StrictMode>,

      at Object.<anonymous> (src/tests/index.test.tsx:22:27)


  ● LoginScreen › displays a sign in to Nimble message

    TestingLibraryElementError: Unable to find an element with the text: Sign in to Nimble. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    <body
      class="login-screen"
    >
      <div>
        <div
          class="login-screen__grid"
        >
          <div
            class="login-screen__container"
            data-test-id="login-screen-container"
          >
            <div
              class="login-form"
            >
              <img
                alt="login.nimble_logo_alt"
                class="login-form__header-image"
                src="nimble-logo.png"
              />
              <div
                class="login-form__header-title"
              >
                login.title
              </div>
              <form
                action="#"
              >
                <div
                  class="login-form__field"
                >
                  <label
                    class="login-form__label"
                    for="email"
                  >
                    login.email
                  </label>
                  <input
                    class="login-form__input form-control"
                    id="email"
                    name="email"
                    type="email"
                    value=""
                  />
                </div>
                <div
                  class="login-form__field"
                >
                  <label
                    class="login-form__label"
                    for="password"
                  >
                    login.password
                  </label>
                  <input
                    class="login-form__input form-control"
                    id="password"
                    name="password"
                    type="password"
                    value=""
                  />
                </div>
                <div
                  class="login-form__field"
                >
                  <button
                    class="btn btn-light"
                    type="submit"
                  >
                    login.submit
                  </button>
                </div>
              </form>
            </div>
          </div>
        </div>
      </div>
    </body>

      10 |     const loginScreen = render(<LoginScreen />);
      11 |
    > 12 |     expect(loginScreen.getByText(signInText)).toBeVisible();
         |                        ^
      13 |   });
      14 |
      15 |   it('adds the screen class to body', () => {

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
      at node_modules/@testing-library/dom/dist/query-helpers.js:90:38
      at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
      at getByText (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
      at Object.<anonymous> (src/tests/Login/index.test.tsx:12:24)


  ● BaseAlert › correctly renders

    expect(received).toMatchSnapshot()

    Snapshot name: `BaseAlert correctly renders 1`

    - Snapshot  - 5
    + Received  + 5

    @@ -7,21 +7,21 @@
            >
              <div
                class="base-alert__icon"
              >
                <img
    -             alt="Error icon"
    +             alt="base_alert.error_icon_alt"
                  src="icon-error.svg"
                />
              </div>
              <div
                class="base-alert__content"
              >
                <h5
                  class="base-alert__content-title"
                >
    -             Error
    +             base_alert.error_title
                </h5>
                <div
                  class="base-alert__content-message"
                  data-test-id="base-alert-message"
                >
    @@ -29,29 +29,29 @@
                </div>
              </div>
            </div>
          </div>
        </body>,
    -   "container": <div>
    +   "container": <div>
          <div
            class="base-alert alert alert-dark"
          >
            <div
              class="base-alert__icon"
            >
              <img
    -           alt="Error icon"
    +           alt="base_alert.error_icon_alt"
                src="icon-error.svg"
              />
            </div>
            <div
              class="base-alert__content"
            >
              <h5
                class="base-alert__content-title"
              >
    -           Error
    +           base_alert.error_title
              </h5>
              <div
                class="base-alert__content-message"
                data-test-id="base-alert-message"
              >

       9 |     const baseAlert = render(<BaseAlert>{message}</BaseAlert>);
      10 |
    > 11 |     expect(baseAlert).toMatchSnapshot();
         |                       ^
      12 |   });
      13 |
      14 |   it('displays the message', () => {

      at Object.<anonymous> (src/tests/components/BaseAlert/index.test.tsx:11:23)


  ● App › renders a BackgroundImage

    TypeError: Cannot read property 'map' of undefined

      10 |   return (
      11 |     <ul className="language-switch">
    > 12 |       {i18n.languages.map((language) => {
         |                       ^
      13 |         return (
      14 |           <li key={language}>
      15 |             <a

      at LanguageSwitch (src/components/LanguageSwitch/index.tsx:12:23)
      at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:14985:18)
      at mountIndeterminateComponent (node_modules/react-dom/cjs/react-dom.development.js:17811:13)
      at beginWork (node_modules/react-dom/cjs/react-dom.development.js:19049:16)
      at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:3945:14)
      at HTMLUnknownElement.callTheUserObjectsOperation (node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:338:25)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:221:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:94:17)
      at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:231:34)
      at Object.invokeGuardedCallbackDev (node_modules/react-dom/cjs/react-dom.development.js:3994:16)
      at invokeGuardedCallback (node_modules/react-dom/cjs/react-dom.development.js:4056:31)
      at beginWork$1 (node_modules/react-dom/cjs/react-dom.development.js:23964:7)
      at performUnitOfWork (node_modules/react-dom/cjs/react-dom.development.js:22779:12)
      at workLoopSync (node_modules/react-dom/cjs/react-dom.development.js:22707:5)
      at renderRootSync (node_modules/react-dom/cjs/react-dom.development.js:22670:7)
      at performSyncWorkOnRoot (node_modules/react-dom/cjs/react-dom.development.js:22293:18)
      at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:21881:7)
      at updateContainer (node_modules/react-dom/cjs/react-dom.development.js:25482:3)
      at node_modules/react-dom/cjs/react-dom.development.js:26021:7
      at unbatchedUpdates (node_modules/react-dom/cjs/react-dom.development.js:22431:12)
      at legacyRenderSubtreeIntoContainer (node_modules/react-dom/cjs/react-dom.development.js:26020:5)
      at Object.render (node_modules/react-dom/cjs/react-dom.development.js:26103:10)
      at node_modules/@testing-library/react/dist/pure.js:98:25
      at batchedUpdates$1 (node_modules/react-dom/cjs/react-dom.development.js:22380:12)
      at act (node_modules/react-dom/cjs/react-dom-test-utils.development.js:1042:14)
      at render (node_modules/@testing-library/react/dist/pure.js:94:26)
      at Object.<anonymous> (src/App.test.tsx:6:17)

Report generated by 🧪jest coverage report action from 713b0a6

Please sign in to comment.