-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Codebytes): add tests for codebytes package disc 399 (#21)
* working version * update to include gamut styles * add simple code editor * add editor container styles * fix prettier issues * pass onCopy from parent * use isIframeProp * pass down snippets base url from parent * simple monaco editor * update tslint for scale * incorporate review feedback * fix prettier issues * default snippets endpoint to empty string * use rem * update example text * use language prop * add language selection comp * use colors gray * remove env file * run prettier * change function name to onEdit * update story names * comment on change handlers * update stories * fix prettier * pairing session - new props, types, and passing on handler * use monaco-editor/react package * update monaco editor package version * remove comments * add additional story for language and text not provided * update text * update stories * update stories * fix typings * remove gitignore * use theme navy * fix linting issues * update dependencies * slight change in editorOnMount naming * fix linting issues * rename env * remove eslint comment * fix lint * migrate codebytes tracking * Added types file * Remove Pick * update select * Added default for on * fix linter error * Remove TODO * changed type to interface * Removing "on" param and embedding tracking directly in like static sites * Remove eslint diable * lint fix * added dependency to package.json * use pascal case for language option * fix lint * some quick fixes with bana * codebytes tracking * add editor tests * create shared mock file * add track user impression tests * add tracking tests * add language selection test * add helpers tests * update config * ci test * try adding tracking lib back * adding onCopy prop back in * change to interface * 1 empty line after import statement * call track user impression only if called from forums * use isForums prop for tests * fix identifier * format types * try yarn focus * try running codebytes command * rename to sibling dependencies * replace useEffect for initialText with function * clear merge conflicts * change to is iframe Co-authored-by: Hailey <[email protected]>
- Loading branch information
Showing
16 changed files
with
566 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('../../jest.config.base')('codebytes'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}; | ||
|
||
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' | ||
); | ||
}); | ||
}); | ||
|
||
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', | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.