Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
489a0c1
working version
BandanaKM Dec 17, 2021
f61ddd9
update to include gamut styles
BandanaKM Dec 17, 2021
a90a11a
add simple code editor
BandanaKM Dec 20, 2021
db68a21
add editor container styles
BandanaKM Dec 21, 2021
19caf83
fix prettier issues
BandanaKM Dec 21, 2021
9d702d0
pass onCopy from parent
BandanaKM Dec 21, 2021
7cb1098
formatting
BandanaKM Dec 21, 2021
7cd9589
use isIframeProp
BandanaKM Dec 21, 2021
f7fcbbb
pass down snippets base url from parent
BandanaKM Dec 21, 2021
8efb881
simple monaco editor
BandanaKM Jan 3, 2022
f546671
update tslint for scale
BandanaKM Jan 3, 2022
ddee0b1
incorporate review feedback
BandanaKM Jan 3, 2022
675361b
fix prettier issues
BandanaKM Jan 3, 2022
263a187
default snippets endpoint to empty string
BandanaKM Jan 4, 2022
d70fea1
resolve merge conflicts
BandanaKM Jan 4, 2022
a29b647
resolve merge conflicts
BandanaKM Jan 4, 2022
d8f87b0
use rem
BandanaKM Jan 5, 2022
7386df0
update example text
BandanaKM Jan 5, 2022
01015e9
use language prop
BandanaKM Jan 5, 2022
094d6b5
add language selection comp
BandanaKM Jan 5, 2022
33f7ab3
use colors gray
BandanaKM Jan 6, 2022
47b7061
remove env file
BandanaKM Jan 6, 2022
cfe771d
run prettier
BandanaKM Jan 7, 2022
0e05670
change function name to onEdit
BandanaKM Jan 7, 2022
2c28200
update story names
BandanaKM Jan 7, 2022
3da9ab8
comment on change handlers
BandanaKM Jan 7, 2022
80c1433
update stories
BandanaKM Jan 7, 2022
9c7a196
fix prettier
BandanaKM Jan 7, 2022
6d1aaf6
pairing session - new props, types, and passing on handler
Jan 10, 2022
f3c23df
use monaco-editor/react package
BandanaKM Jan 10, 2022
5c3ffca
update monaco editor package version
BandanaKM Jan 11, 2022
f45f382
remove comments
BandanaKM Jan 11, 2022
524b823
update monaco editor package
BandanaKM Jan 11, 2022
caa7beb
add additional story for language and text not provided
BandanaKM Jan 11, 2022
fd5c9d4
update text
BandanaKM Jan 11, 2022
57a3884
update stories
BandanaKM Jan 11, 2022
9a17a9e
update stories
BandanaKM Jan 11, 2022
1ed659b
fix typings
BandanaKM Jan 11, 2022
808d0db
remove gitignore
BandanaKM Jan 11, 2022
b5af7ab
use theme navy
BandanaKM Jan 11, 2022
73a4d07
fix linting issues
BandanaKM Jan 11, 2022
8c2c055
update dependencies
BandanaKM Jan 11, 2022
20333e7
update color source
BandanaKM Jan 11, 2022
6ea9e6a
slight change in editorOnMount naming
BandanaKM Jan 11, 2022
7497321
fix linting issues
BandanaKM Jan 11, 2022
4f65b1b
rename env
BandanaKM Jan 11, 2022
5fb2303
Merge branch 'hr-bm-move-language-selection-component-disc-354' of gi…
Jan 11, 2022
1bbcee4
remove eslint comment
Jan 12, 2022
74f0fd0
Merge branch 'bm-add-simple-monaco-editor-disc-353' into hr-bm-move-l…
BandanaKM Jan 12, 2022
cae44b6
Merge branch 'hr-bm-codebytes-tracking' of github.com:Codecademy/clie…
BandanaKM Jan 12, 2022
c032684
resolve merge conflicts and updated copy
BandanaKM Jan 13, 2022
b79ad65
fix lint
BandanaKM Jan 13, 2022
aae5d54
migrate codebytes tracking
BandanaKM Jan 13, 2022
26e0113
Added types file
Jan 13, 2022
2eff1d8
Remove Pick
Jan 13, 2022
405be71
update select
BandanaKM Jan 13, 2022
cef9358
Added default for on
Jan 13, 2022
aaa799c
fix linter error
BandanaKM Jan 14, 2022
3e52307
Merge branch 'hr-bm-move-language-selection-component-disc-354' into …
BandanaKM Jan 14, 2022
9e23207
Merge branch 'hr-bm-migrate-codebytes-tracking-disc-355' of github.co…
BandanaKM Jan 14, 2022
f31f337
Remove TODO
Jan 14, 2022
1665549
Merge branch 'hr-bm-migrate-codebytes-tracking-disc-355' of github.co…
Jan 14, 2022
05abbbb
changed type to interface
Jan 19, 2022
c6c8718
Removing "on" param and embedding tracking directly in like static sites
Jan 20, 2022
6d07c23
Remove eslint diable
Jan 20, 2022
c13107f
lint fix
Jan 21, 2022
35ce764
added dependency to package.json
Jan 21, 2022
8f8ccd8
use pascal case for language option
BandanaKM Jan 21, 2022
5172376
fix lint
BandanaKM Jan 21, 2022
e58974c
Merge branch 'hr-bm-move-language-selection-component-disc-354' of gi…
Jan 21, 2022
16a37df
some quick fixes with bana
Jan 21, 2022
e45de07
Merge branch 'main' of github.com:Codecademy/client-modules into hr-b…
Jan 21, 2022
c4a9928
codebytes tracking
BandanaKM Jan 22, 2022
23485c5
add editor tests
BandanaKM Jan 22, 2022
4761e28
create shared mock file
BandanaKM Jan 22, 2022
2640fa3
add track user impression tests
BandanaKM Jan 22, 2022
450b331
add tracking tests
BandanaKM Jan 22, 2022
9be1da4
add language selection test
BandanaKM Jan 22, 2022
9990814
add helpers tests
BandanaKM Jan 22, 2022
8ec58f7
Merge branch 'hr-bm-migrate-codebytes-tracking-disc-355' into bm-add-…
BandanaKM Jan 23, 2022
725473e
update config
BandanaKM Jan 23, 2022
3543385
Merge branch 'bm-add-tests-for-codebytes-package-disc-399' of github.…
BandanaKM Jan 23, 2022
ea7234b
ci test
BandanaKM Jan 23, 2022
b5894cc
try adding tracking lib back
BandanaKM Jan 23, 2022
57228ad
adding onCopy prop back in
Jan 24, 2022
aadc550
change to interface
Jan 24, 2022
80ebbb9
1 empty line after import statement
Jan 24, 2022
b2b8cc2
call track user impression only if called from forums
BandanaKM Jan 25, 2022
f8fe5ce
use isForums prop for tests
BandanaKM Jan 25, 2022
10e6146
Merge branch 'hr-bm-migrate-codebytes-tracking-disc-355' into bm-add-…
BandanaKM Jan 25, 2022
7315cc4
fix identifier
BandanaKM Jan 25, 2022
a75010f
format types
BandanaKM Jan 25, 2022
7505e3f
try yarn focus
BandanaKM Jan 25, 2022
be6a662
try running codebytes command
BandanaKM Jan 25, 2022
9c97400
rename to sibling dependencies
BandanaKM Jan 26, 2022
7e1da41
replace useEffect for initialText with function
BandanaKM Jan 27, 2022
e2bc641
resolve merge conflicts
BandanaKM Jan 29, 2022
bfc8a83
clear merge conflicts
BandanaKM Jan 29, 2022
a075a9f
change to is iframe
BandanaKM Jan 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ jobs:
- *set_npm_token
- *restore_yarn_cache
- run: yarn --production=false --frozen-lockfile
- run: yarn run install:sibling-dependencies
- *save_yarn_cache
- *save_node_modules

Expand Down
5 changes: 5 additions & 0 deletions jest.config.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ module.exports = (packageName) => ({
snapshotSerializers: ['enzyme-to-json/serializer'],
moduleDirectories: ['node_modules'],
collectCoverageFrom: ['<rootDir>/**/*.{js,jsx,ts,tsx}'],
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/../../script/jest/fileMock',
'\\.(css|scss)$': '<rootDir>/../../script/jest/styleMock',
},
Copy link
Copy Markdown
Contributor Author

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

coveragePathIgnorePatterns: [
'/node_modules/',
'/stories/',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"build-all": "lerna run lernaBuildTask",
"build-changed": "lerna run lernaBuildTask --since --include-dependencies",
"start:storybook": "cd ./packages/styleguide && yarn start",
"install:sibling-dependencies": "cd ./packages/codebytes && yarn --focus",
"start": "yarn && yarn start:storybook"
},
"lint-staged": {
Expand Down
1 change: 1 addition & 0 deletions packages/codebytes/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../../jest.config.base')('codebytes');
12 changes: 10 additions & 2 deletions packages/codebytes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
"@emotion/styled": "^11.3.0",
"@monaco-editor/react": "4.3.1",
"monaco-editor": ">= 0.25.0 < 1",
"react-resize-observer": "1.1.1"
"react-resize-observer": "1.1.1",
"js-base64": "^3.6.0",
"jsuri": "^1.3.1",
"@codecademy/tracking": "0.18.0"
},
"scripts": {
"verify": "tsc --noEmit",
Expand All @@ -44,7 +47,12 @@
"@emotion/jest": "^11.3.0",
"@testing-library/dom": "^7.31.2",
"@testing-library/react": "^11.0.4",
"@types/loadable__component": "^5.13.2"
"@types/loadable__component": "^5.13.2",
"@types/jsuri": "^1.3.30",
"@testing-library/react-hooks": "3.2.1",
"@testing-library/user-event": "13.1.1",
"monaco-editor-webpack-plugin": "1.9.1",
"@codecademy/gamut-tests": "*"
},
"publishConfig": {
"access": "public"
Expand Down
197 changes: 197 additions & 0 deletions packages/codebytes/src/__tests__/codebyte-test.tsx
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);
};
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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'
);
});
});
Copy link
Copy Markdown
Contributor Author

@BandanaKM BandanaKM Jan 22, 2022

Choose a reason for hiding this comment

The 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',
});
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The tracking functions tests are pretty much verbatim from static sites.

});
});
});
113 changes: 113 additions & 0 deletions packages/codebytes/src/__tests__/editor-test.tsx
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');
});
});
});
Loading