-
Notifications
You must be signed in to change notification settings - Fork 0
feat(Codebytes): add tests for codebytes package disc 399 #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
489a0c1
f61ddd9
a90a11a
db68a21
19caf83
9d702d0
7cb1098
7cd9589
f7fcbbb
8efb881
f546671
ddee0b1
675361b
263a187
d70fea1
a29b647
d8f87b0
7386df0
01015e9
094d6b5
33f7ab3
47b7061
cfe771d
0e05670
2c28200
3da9ab8
80c1433
9c7a196
6d1aaf6
f3c23df
5c3ffca
f45f382
524b823
caa7beb
fd5c9d4
57a3884
9a17a9e
1ed659b
808d0db
b5af7ab
73a4d07
8c2c055
20333e7
6ea9e6a
7497321
4f65b1b
5fb2303
1bbcee4
74f0fd0
cae44b6
c032684
b79ad65
aae5d54
26e0113
2eff1d8
405be71
cef9358
aaa799c
3e52307
9e23207
f31f337
1665549
05abbbb
c6c8718
6d07c23
c13107f
35ce764
8f8ccd8
5172376
e58974c
16a37df
e45de07
c4a9928
23485c5
4761e28
2640fa3
450b331
9be1da4
9990814
8ec58f7
725473e
3543385
ea7234b
b5894cc
57228ad
aadc550
80ebbb9
b2b8cc2
f8fe5ce
10e6146
7315cc4
a75010f
7505e3f
be6a662
9c97400
7e1da41
e2bc641
bfc8a83
a075a9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| module.exports = require('../../jest.config.base')('codebytes'); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| import './mocks'; | ||
|
|
||
| import { setupRtl } from '@codecademy/gamut-tests'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import { encode } from 'js-base64'; | ||
| import React from 'react'; | ||
|
|
||
| import { CodeByteEditor } from '..'; | ||
| import { helloWorld, validLanguages } from '../consts'; | ||
| import { trackClick } from '../helpers'; | ||
| import { trackUserImpression } from '../libs/eventTracking'; | ||
| import { CodeByteEditorProps } from '../types'; | ||
|
|
||
| const mockEditorTestId = 'mock-editor-test-id'; | ||
|
|
||
| // This is a super simplified mock capable of render value and trigger onChange. | ||
| jest.mock('../MonacoEditor', () => ({ | ||
| SimpleMonacoEditor: ({ | ||
| value, | ||
| onChange, | ||
| }: { | ||
| value: string; | ||
| onChange?: (value: string) => void; | ||
| }) => ( | ||
| <> | ||
| {value} | ||
| <input | ||
| data-testid={mockEditorTestId} | ||
| type="text" | ||
| onChange={(e) => { | ||
| onChange?.(e.target.value); | ||
| }} | ||
| value={value} | ||
| /> | ||
| </> | ||
| ), | ||
| })); | ||
|
|
||
| const renderWrapper = setupRtl(CodeByteEditor, {}); | ||
|
|
||
| type RenderWrapperWithProps = CodeByteEditorProps & { mode?: string }; | ||
|
|
||
| const renderWrapperWith = ({ mode, ...rest }: RenderWrapperWithProps) => { | ||
| const url = new URL(window.location.href); | ||
|
|
||
| const { text, language } = rest; | ||
| if (text) { | ||
| url.searchParams.set('text', encode(text)); | ||
| } | ||
| if (language) { | ||
| url.searchParams.set('lang', language); | ||
| } | ||
| url.searchParams.set('client-name', 'forum'); | ||
| url.searchParams.set( | ||
| 'page', | ||
| 'https://discuss.codecademy.com/some-interesting/post' | ||
| ); | ||
| if (mode) { | ||
| url.searchParams.set('mode', mode); | ||
| } | ||
| window.history.replaceState({}, '', url.toString()); | ||
|
|
||
| return renderWrapper(rest); | ||
| }; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function sets query params on the Codebytes component for testing purposes. By using 'rest' here, we are forwarding all the props from the Codebytes component, while also allowing mode to be set to the query params value. |
||
|
|
||
| describe('CodeBytes', () => { | ||
| const initialUrl = window.location.href; | ||
|
|
||
| afterEach(() => { | ||
| window.history.replaceState(null, '', initialUrl); | ||
| (trackClick as any).mockReset(); | ||
| (trackUserImpression as any).mockReset(); | ||
| }); | ||
|
|
||
| it('has a language-specific "hello world" program defined for each language', () => { | ||
| validLanguages.forEach((language) => { | ||
| expect(helloWorld[language]).toBeDefined(); | ||
| }); | ||
| }); | ||
|
|
||
| it('initializes with a language-specific "hello world" program when there is no language prop', () => { | ||
| const { view } = renderWrapper(); | ||
| const selectedLanguage = view.getByRole('combobox') as Element; | ||
| userEvent.selectOptions(selectedLanguage, ['javascript']); | ||
| view.getByText(helloWorld.javascript); | ||
| }); | ||
|
|
||
| it('initializes with a language-specific "hello world" program when there is a language prop but no text prop', () => { | ||
| const { view } = renderWrapper({ language: 'javascript' }); | ||
| view.getByText(helloWorld.javascript); | ||
| }); | ||
|
|
||
| it('initializes with deserialized text when there is a text prop but no language prop', () => { | ||
| const testString = 'yes hello'; | ||
| const { view } = renderWrapper({ text: testString }); | ||
| const selectedLanguage = view.getByRole('combobox') as Element; | ||
| userEvent.selectOptions(selectedLanguage, ['javascript']); | ||
| view.getByText(testString); | ||
| }); | ||
|
|
||
| it('initializes with deserialized text when there is both a language and text prop', () => { | ||
| const testString = 'yes hello'; | ||
| const { view } = renderWrapper({ | ||
| text: testString, | ||
| language: 'javascript', | ||
| }); | ||
| view.getByText(testString); | ||
| }); | ||
|
|
||
| describe('Change Handlers', () => { | ||
| it('triggers onEdit on text edit', () => { | ||
| const onEdit = jest.fn(); | ||
| const { view } = renderWrapper({ | ||
| text: '', | ||
| language: 'javascript', | ||
| onEdit, | ||
| }); | ||
|
|
||
| const editor = view.getByTestId(mockEditorTestId); | ||
| userEvent.type(editor, 'dog'); | ||
|
|
||
| expect(onEdit).toHaveBeenCalledTimes(3); | ||
| expect(onEdit).toHaveBeenLastCalledWith('dog', 'javascript'); | ||
| }); | ||
|
|
||
| it('triggers onLanguageChange on language selection', () => { | ||
| const onLanguageChange = jest.fn(); | ||
| const { view } = renderWrapper({ | ||
| onLanguageChange, | ||
| }); | ||
|
|
||
| const selectedLanguage = view.getByRole('combobox') as Element; | ||
| userEvent.selectOptions(selectedLanguage, ['javascript']); | ||
|
|
||
| expect(onLanguageChange).toHaveBeenCalledWith( | ||
| "console.log('Hello world!');", | ||
| 'javascript' | ||
| ); | ||
| }); | ||
| }); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still need a couple change handler functions, because these differ in the forums version and the regular codebytes. Here we can pass custom functions into the changeHandlers as needed, for some forum specific logic. |
||
|
|
||
| describe('Tracking', () => { | ||
| it('triggers trackClick on clicking the logo', () => { | ||
| const { view } = renderWrapper({}); | ||
| const logo = view.getByLabelText('visit codecademy.com'); | ||
| userEvent.click(logo); | ||
| expect(trackClick).toHaveBeenCalledWith('logo'); | ||
| }); | ||
|
|
||
| it('triggers trackClick on language selection', () => { | ||
| const { view } = renderWrapper(); | ||
| const selectedLanguage = view.getByRole('combobox') as Element; | ||
| userEvent.selectOptions(selectedLanguage, ['javascript']); | ||
| expect(trackClick).toHaveBeenCalledWith('lang_select'); | ||
| }); | ||
|
|
||
| it('triggers trackClick for the first edit in view mode', () => { | ||
| const testString = 'original-value'; | ||
| const { view } = renderWrapper({ | ||
| text: testString, | ||
| language: 'javascript', | ||
| }); | ||
|
|
||
| const editor = view.getByTestId(mockEditorTestId); | ||
| userEvent.type(editor, 'd'); | ||
|
|
||
| expect(trackClick).toHaveBeenCalledWith('edit'); | ||
| }); | ||
|
|
||
| it('triggers trackUserImpression for view mode', () => { | ||
| renderWrapperWith({ | ||
| text: 'some-value', | ||
| language: 'javascript', | ||
| }); | ||
|
|
||
| expect(trackUserImpression).toHaveBeenCalledWith({ | ||
| page_name: 'forum', | ||
| context: 'https://discuss.codecademy.com/some-interesting/post', | ||
| target: 'codebyte', | ||
| }); | ||
| }); | ||
|
|
||
| it('triggers trackUserImpression for compose mode', () => { | ||
| renderWrapperWith({ | ||
| text: 'some-value', | ||
| language: 'javascript', | ||
| mode: 'compose', | ||
| }); | ||
|
|
||
| expect(trackUserImpression).toHaveBeenCalledWith({ | ||
| page_name: 'forum_compose', | ||
| context: 'https://discuss.codecademy.com/some-interesting/post', | ||
| target: 'codebyte', | ||
| }); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tracking functions tests are pretty much verbatim from static sites. |
||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import './mocks'; | ||
|
|
||
| import { setupRtl } from '@codecademy/gamut-tests'; | ||
| import { act } from '@testing-library/react'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import React from 'react'; | ||
|
|
||
| import { Editor } from '../editor'; | ||
| import { trackClick } from '../helpers'; | ||
|
|
||
| jest.mock('../MonacoEditor', () => ({ | ||
| SimpleMonacoEditor: ({ value }: { value: string }) => <>{value}</>, | ||
| })); | ||
|
|
||
| const renderWrapper = setupRtl(Editor, { | ||
| hideCopyButton: false, | ||
| language: 'javascript', | ||
| text: 'hello world', | ||
| onChange: jest.fn(), | ||
| snippetsBaseUrl: '', | ||
| }); | ||
|
|
||
| Object.defineProperty(navigator, 'clipboard', { | ||
| value: { | ||
| writeText: jest.fn().mockImplementation(() => Promise.resolve()), | ||
| }, | ||
| }); | ||
|
|
||
| describe('Editor', () => { | ||
| (global as any).fetch = jest.fn(); | ||
| afterEach(() => { | ||
| (global as any).fetch.mockClear(); | ||
| }); | ||
|
|
||
| it('shows a prompt tooltip when the CodeByte has __not__ been copied via the button', () => { | ||
| const { view } = renderWrapper(); | ||
| expect(view.queryByTestId('copy-confirmation-tooltip')).toBeFalsy(); | ||
| view.getByTestId('copy-prompt-tooltip'); | ||
| }); | ||
|
|
||
| it('shows a confirmation tooltip when the CodeByte has been copied via the button', () => { | ||
| const { view } = renderWrapper(); | ||
| const copyBtn = view.getByTestId('copy-codebyte-btn'); | ||
| userEvent.click(copyBtn as HTMLButtonElement); | ||
| expect(view.queryByTestId('copy-prompt-tooltip')).toBeFalsy(); | ||
| view.getByTestId('copy-confirmation-tooltip'); | ||
| }); | ||
|
|
||
| it('hides the copy codebyte button if hideCopyButton prop is true"', () => { | ||
| const { view } = renderWrapper({ | ||
| hideCopyButton: true, | ||
| }); | ||
| expect(view.queryByTestId('copy-codebyte-btn')).toBeNull(); | ||
| }); | ||
|
|
||
| it('shows the copy codebyte button if hideCopyButton prop is not set', () => { | ||
| const { view } = renderWrapper(); | ||
|
|
||
| view.getByTestId('copy-codebyte-btn'); | ||
| }); | ||
|
|
||
| describe('Change Handlers', () => { | ||
| it('triggers onCopy upon clicking the copy button', () => { | ||
| const onCopy = jest.fn(); | ||
| const { view } = renderWrapper({ | ||
| onCopy, | ||
| }); | ||
|
|
||
| const copyButton = view.getByTestId('copy-codebyte-btn'); | ||
| userEvent.click(copyButton); | ||
|
|
||
| expect(onCopy).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Tracking', () => { | ||
| it('tracks clicks on the run button', async () => { | ||
| (global as any).fetch.mockResolvedValue({ | ||
| json: () => | ||
| Promise.resolve({ | ||
| stderr: [], | ||
| exit_code: 0, | ||
| stdout: '', | ||
| }), | ||
| }); | ||
| const { view } = renderWrapper({ | ||
| onChange: jest.fn(), | ||
| text: 'test', | ||
| language: 'javascript', | ||
| }); | ||
|
|
||
| const runButton = view.getByText('Run'); | ||
| await act(async () => { | ||
| userEvent.click(runButton); | ||
| }); | ||
|
|
||
| expect(trackClick).toHaveBeenCalledWith('run'); | ||
| }); | ||
|
|
||
| it('tracks clicks on the copy codebyte button', () => { | ||
| const { view } = renderWrapper({ | ||
| onChange: jest.fn(), | ||
| text: 'test', | ||
| language: 'javascript', | ||
| }); | ||
|
|
||
| const copyButton = view.getByTestId('copy-codebyte-btn'); | ||
| userEvent.click(copyButton); | ||
|
|
||
| expect(trackClick).toHaveBeenCalledWith('copy'); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows us to use mocks for css instead of the actual assets; copied this behavior from gamut