Skip to content

Commit

Permalink
feat: switch to testing-library
Browse files Browse the repository at this point in the history
  • Loading branch information
kellyjosephprice committed Apr 5, 2024
1 parent 5c2c0fe commit 353e5b8
Show file tree
Hide file tree
Showing 11 changed files with 654 additions and 477 deletions.
206 changes: 95 additions & 111 deletions __tests__/__snapshots__/codeMirror.test.js.snap

Large diffs are not rendered by default.

43 changes: 24 additions & 19 deletions __tests__/codeEditor.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mount } from 'enzyme';
import { render, screen, waitFor } from '@testing-library/react';
import React from 'react';

import CodeEditor from '../src/codeEditor';
Expand All @@ -8,43 +8,48 @@ describe('<CodeEditor/>', () => {
Range.prototype.getBoundingClientRect = getClientRectSpy;
Range.prototype.getClientRects = getClientRectSpy;

const node = mount(<CodeEditor code="console.log('Hello, world.');" lang="js" />);
const cm = node.find('Controlled');

it('should display a <CodeEditor> element', () => {
expect(node.children('.CodeEditor')).toHaveLength(1);
render(<CodeEditor code="console.log('Hello, world.');" lang="javascript" />);

expect(screen.getByRole('textbox')).toBeVisible();
});

it('should highlight code', () => {
expect(cm.html()).toContain('cm-variable');
render(<CodeEditor code="console.log('Hello, world.');" lang="javascript" />);

expect(screen.getByText('console')).toHaveClass('cm-variable');
});

it('should set CodeMirror options', () => {
expect('options' in cm.props()).toBe(true);
render(<CodeEditor code="console.log('Hello, world.');" lang="javascript" theme="neo" />);
// eslint-disable-next-line testing-library/no-node-access
const [editor] = document.getElementsByClassName('CodeEditor');

// eslint-disable-next-line testing-library/no-node-access
expect(editor.children[0]).toHaveClass('cm-s-neo');
});

it('should set a sanitized language mode', () => {
expect(node.props().lang).toBe('js');
expect(cm.props().options.mode).toBe('javascript');
render(<CodeEditor code="console.log('Hello, world.');" lang="js" />);

expect(screen.getByText('console')).toHaveClass('cm-variable');
});

it('should set new language via props', () => {
node.setProps({ lang: 'kotlin' });
expect(node.props().lang).toBe('kotlin');
it.skip('should set new language via props', () => {
const { rerender } = render(<CodeEditor code="console.log('Hello, world.');" />);
expect(screen.getByText("console.log('Hello, world.');")).toBeVisible();

setTimeout(() => {
expect(cm.props().options.mode).toBe('clike');
});
rerender(<CodeEditor code="console.log('Hello, world.');" lang="javascript" />);
return waitFor(() => expect(screen.getByText('console')).resolves.toHaveClass('cm-variable'));
});

it('should take children as a code value', () => {
const props = {
children: 'const res = true;',
lang: 'js',
lang: 'javascript',
};

const n2 = mount(<CodeEditor {...props} />);
const cm2 = n2.find('Controlled');
expect(cm2.prop('value')).toBe('const res = true;');
render(<CodeEditor {...props} />);
expect(screen.getByText('const')).toBeVisible();
});
});
141 changes: 75 additions & 66 deletions __tests__/codeMirror.test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
/* eslint-disable testing-library/no-node-access */
/* eslint-disable testing-library/no-container */
import { promises as fs } from 'fs';
import path from 'path';

import Variable from '@readme/variable';

Check failure on line 6 in __tests__/codeMirror.test.js

View workflow job for this annotation

GitHub Actions / build (18 w/ React 18

'Variable' is defined but never used

Check failure on line 6 in __tests__/codeMirror.test.js

View workflow job for this annotation

GitHub Actions / build (20 w/ React 18

'Variable' is defined but never used
import { mount, shallow } from 'enzyme';
// eslint-disable-next-line testing-library/no-manual-cleanup
import { render, screen, cleanup } from '@testing-library/react';
import { globSync } from 'glob';

import syntaxHighlighter, { uppercase, canonical } from '../src';

const fixtures = globSync(path.join(__dirname, '/__fixtures__/*'));

test('should highlight a block of code', () => {
const code = shallow(syntaxHighlighter('var a = 1;', 'javascript'));
render(syntaxHighlighter('var a = 1;', 'javascript'));

expect(code.hasClass('cm-s-neo')).toBe(true);
expect(code.html()).toBe(
'<div class="cm-s-neo"><span class="cm-keyword">var</span> <span class="cm-def">a</span> <span class="cm-operator">=</span> <span class="cm-number">1</span>;</div>',
expect(screen.getByTestId('SyntaxHighlighter').outerHTML).toBe(
'<div class="cm-s-neo" data-testid="SyntaxHighlighter"><span class="cm-keyword">var</span> <span class="cm-def">a</span> <span class="cm-operator">=</span> <span class="cm-number">1</span>;</div>',
);
});

Expand All @@ -23,62 +25,64 @@ test('should work when passed a non-string value', () => {
});

test('should sanitize plain text language', () => {
expect(shallow(syntaxHighlighter('& < > " \' /', 'text')).html()).toContain('&amp; &lt; &gt; &quot; &#x27; /');
render(syntaxHighlighter('& < > " \' /', 'text'));
expect(screen.getByText('& < > " \' /')).toBeVisible();
});

test('should sanitize mode', () => {
expect(shallow(syntaxHighlighter('&', 'json')).html()).toContain('&amp;');
expect(shallow(syntaxHighlighter('<', 'json')).html()).toContain('&lt;');
render(syntaxHighlighter('&', 'json'));
expect(screen.getByText('&')).toBeVisible();

render(syntaxHighlighter('<', 'json'));
expect(screen.getByText('<')).toBeVisible();
});

test('should concat the same style items', () => {
// This is testing the `accum += text;` line
expect(shallow(syntaxHighlighter('====', 'javascript')).text()).toContain('====');
render(syntaxHighlighter('====', 'javascript'));
expect(screen.getByText('====')).toBeVisible();
});

test('should work with modes', () => {
expect(shallow(syntaxHighlighter('{ "a": 1 }', 'json')).html()).toBe(
'<div class="cm-s-neo">{ <span class="cm-property">&quot;a&quot;</span>: <span class="cm-number">1</span> }</div>',
render(syntaxHighlighter('{ "a": 1 }', 'json'));

expect(screen.getByTestId('SyntaxHighlighter').outerHTML).toBe(
'<div class="cm-s-neo" data-testid="SyntaxHighlighter">{ <span class="cm-property">"a"</span>: <span class="cm-number">1</span> }</div>',
);
});

test('should keep trailing json bracket if highlightMode is enabled', () => {
expect(
shallow(
syntaxHighlighter('{ "a": 1 }', 'json', {
highlightMode: true,
}),
).html(),
).toBe(
'<div class="cm-s-neo CodeEditor"><div class="CodeMirror"><div class="cm-linerow "><span class="cm-lineNumber">1</span>{ <span class="cm-property">&quot;a&quot;</span>: <span class="cm-number">1</span> }</div></div></div>',
render(syntaxHighlighter('{ "a": 1 }', 'json', { highlightMode: true }));

expect(screen.getByTestId('CodeMirror').outerHTML).toBe(
'<div class="CodeMirror" data-testid="CodeMirror"><div class="cm-linerow "><span class="cm-lineNumber">1</span>{ <span class="cm-property">"a"</span>: <span class="cm-number">1</span> }</div></div>',
);
});

test('should have a dark theme', () => {
expect(shallow(syntaxHighlighter('{ "a": 1 }', 'json', { dark: true })).hasClass('cm-s-material-palenight')).toBe(
true,
);
render(syntaxHighlighter('{ "a": 1 }', 'json', { dark: true }));
expect(screen.getByTestId('SyntaxHighlighter')).toHaveClass('cm-s-material-palenight');
});

describe('variable substitution', () => {
it('should tokenize variables (double quotes)', () => {
expect(mount(syntaxHighlighter('"<<apiKey>>"', 'json', { tokenizeVariables: true })).find(Variable)).toHaveLength(
1,
);
render(syntaxHighlighter('"<<apiKey>>"', 'json', { tokenizeVariables: true }));
expect(screen.getByText('APIKEY')).toBeVisible();
});

it('should tokenize variables (single quotes)', () => {
expect(mount(syntaxHighlighter("'<<apiKey>>'", 'json', { tokenizeVariables: true })).find(Variable)).toHaveLength(
1,
);
render(syntaxHighlighter("'<<apiKey>>'", 'json', { tokenizeVariables: true }));
expect(screen.getByText('APIKEY')).toBeVisible();
});

it('should keep enclosing characters around the variable', () => {
expect(mount(syntaxHighlighter("'<<apiKey>>'", 'json', { tokenizeVariables: true })).text()).toBe("'APIKEY'");
render(syntaxHighlighter("'<<apiKey>>'", 'json', { tokenizeVariables: true }));
expect(screen.getByTestId('SyntaxHighlighter')).toHaveTextContent("'APIKEY'");
});

it('should tokenize variables outside of quotes', () => {
expect(mount(syntaxHighlighter('<<apiKey>>', 'json', { tokenizeVariables: true })).text()).toBe('APIKEY');
render(syntaxHighlighter('<<apiKey>>', 'json', { tokenizeVariables: true }));
expect(screen.getByText('APIKEY')).toBeVisible();
});

it('should tokenize variables outside of quotes over multiple lines', () => {
Expand All @@ -89,19 +93,18 @@ describe('variable substitution', () => {
fetch({ foo, bar, baz: <<token>> });
`;

expect(mount(syntaxHighlighter(codeBlock, 'json', { tokenizeVariables: true })).text()).toMatchSnapshot();
render(syntaxHighlighter(codeBlock, 'json', { tokenizeVariables: true }));
expect(screen.getByTestId('SyntaxHighlighter').textContent).toMatchSnapshot();
});

it('should tokenize multiple variables per line', () => {
expect(mount(syntaxHighlighter('<<apiKey>> <<name>>', 'json', { tokenizeVariables: true })).text()).toBe(
'APIKEY NAME',
);
render(syntaxHighlighter('<<apiKey>> <<name>>', 'json', { tokenizeVariables: true }));
expect(screen.getByTestId('SyntaxHighlighter')).toHaveTextContent('APIKEY NAME');
});

it('should NOT tokenize escaped variables', () => {
expect(mount(syntaxHighlighter('\\<<wat>>', 'json', { tokenizeVariables: true })).text()).toBe('<<wat>>');
expect(mount(syntaxHighlighter('<<wat\\>>', 'json', { tokenizeVariables: true })).text()).toBe('<<wat>>');
expect(mount(syntaxHighlighter('\\<<wat\\>>', 'json', { tokenizeVariables: true })).text()).toBe('<<wat>>');
it.each(['\\<<wat>>', '<<wat\\>>', '\\<<wat\\>>'])('should NOT tokenize escaped variables %s', code => {
render(syntaxHighlighter(code, 'json', { tokenizeVariables: true }));
expect(screen.getByTestId('SyntaxHighlighter')).toHaveTextContent('<<wat>>');
});
});

Expand Down Expand Up @@ -132,8 +135,8 @@ describe('Supported languages', () => {
});

it('should syntax highlight an example', () => {
const highlighted = shallow(syntaxHighlighter(testCase, instructions.mode.primary)).html();
expect(highlighted).toMatchSnapshot();
render(syntaxHighlighter(testCase, instructions.mode.primary));
expect(screen.getByTestId('SyntaxHighlighter').outerHTML).toMatchSnapshot();
});

if (Object.keys(instructions.mode.aliases).length > 0) {
Expand All @@ -142,8 +145,12 @@ describe('Supported languages', () => {
describe('Mode aliases', () => {
describe.each(aliases)('%s', (alias, aliasName) => {
it('should support the mode alias', () => {
const highlighted = shallow(syntaxHighlighter(testCase, instructions.mode.primary)).html();
expect(shallow(syntaxHighlighter(testCase, alias)).html()).toBe(highlighted);
render(syntaxHighlighter(testCase, instructions.mode.primary));
const highlighted = screen.getByTestId('SyntaxHighlighter').outerHTML;
cleanup();

render(syntaxHighlighter(testCase, alias));
expect(screen.getByTestId('SyntaxHighlighter').outerHTML).toBe(highlighted);
});

it('should uppercase the mode alias', () => {
Expand All @@ -166,25 +173,29 @@ describe('Supported languages', () => {
if (instructions.mode.primary === 'html') {
it('should highlight handlebars templates', () => {
const code = '<p>{{firstname}} {{lastname}}</p>';
expect(shallow(syntaxHighlighter(code, 'handlebars')).html()).toContain('cm-bracket');
const { container } = render(syntaxHighlighter(code, 'handlebars'));

expect(container.querySelector('.cm-bracket')).toBeVisible();
});
} else if (instructions.mode.primary === 'php') {
it('should highlight if missing an opening `<?php` tag', () => {
expect(shallow(syntaxHighlighter('echo "Hello World";', 'php')).html()).toContain('cm-keyword');
const code = 'echo "Hello World";';
const { container } = render(syntaxHighlighter(code, 'php'));

expect(container.querySelector('.cm-keyword')).toBeVisible();
});
}
});
});

describe('highlight mode', () => {
let node;
const code = `curl --request POST
--url <<url>>
--header 'authorization: Bearer 123'
--header 'content-type: application/json'`;

beforeEach(() => {
node = mount(
const defaultRender = () =>
render(
syntaxHighlighter(code, 'curl', {
dark: true,
highlightMode: true,
Expand All @@ -197,32 +208,34 @@ describe('highlight mode', () => {
],
}),
);
});

it('should return line numbers by default', () => {
expect(node.find('span').first().hasClass('cm-lineNumber')).toBe(true);
const { container } = defaultRender();
expect(container.querySelector('.cm-lineNumber')).toBeVisible();
});

it('should convert variable regex matches to a component instance', () => {
expect(node.find(Variable)).toHaveLength(1);
it('should highlight variables', () => {
defaultRender();
expect(screen.getByText('URL')).toBeVisible();
});

it('should highlight based on range input', () => {
expect(node.find('.cm-linerow.cm-highlight')).toHaveLength(2);
const { container } = defaultRender();
expect(container.querySelector('.cm-linerow.cm-highlight')).toHaveTextContent('1curl --request POST');
});

it('should add an overlay to non-highlighted in lines when ranges are applied', () => {
expect(node.find('.cm-linerow.cm-overlay')).toHaveLength(6);
const { container } = defaultRender();
expect(container.querySelectorAll('.cm-linerow.cm-overlay')).toHaveLength(3);
});
});

describe('runmode', () => {
let node;
const code =
'CURL *hnd = curl_easy_init();\n\nurl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "GET");\n\ncurl_easy_setopt(hnd, CURLOPT_URL, "http://httpbin.orgpet/");';

beforeEach(() => {
node = mount(
it('should display the correct number of lines with multiple linebreaks', () => {
render(
syntaxHighlighter(code, 'c', {
dark: true,
highlightMode: true,
Expand All @@ -235,13 +248,8 @@ describe('runmode', () => {
],
}),
);
});

it('should display the correct number of lines with multiple linebreaks', () => {
const checkLineBreaks = parseInt(node.find('.cm-linerow').last().find('.cm-lineNumber').text(), 10);
const totalLines = code.split('\n');

expect(checkLineBreaks).toStrictEqual(totalLines.length);
expect(screen.getAllByText(/\d/)).toHaveLength(5);
});
});

Expand All @@ -263,10 +271,11 @@ describe('code folding', () => {
};
});

it('relevant options in the props matches snapshot', () => {
const { options } = shallow(
it('renders folders in the gutter', () => {
const { container } = render(
syntaxHighlighter('{ "a": { "b": { "c": 1 } }', 'json', { foldGutter: true, readOnly: true }),
).props();
expect(options).toMatchSnapshot();
);

expect(container.querySelector('.CodeMirror-foldgutter')).toBeVisible();
});
});
1 change: 1 addition & 0 deletions __tests__/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom';
4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ module.exports = {
moduleNameMapper: {
'^.+\\.(css|less|scss)$': 'identity-obj-proxy',
},
setupFiles: [path.join(__dirname, '/lib/enzyme')],
setupFilesAfterEnv: [path.join(__dirname, '__tests__/jest.setup.js')],
testEnvironment: 'jsdom',
testPathIgnorePatterns: ['<rootDir>/__tests__/__fixtures__/'],
testPathIgnorePatterns: ['<rootDir>/__tests__/__fixtures__/', '<rootDir>/__tests__/jest.setup.js'],
transform: {
'^.+\\.jsx?$': 'babel-jest',
},
Expand Down
16 changes: 0 additions & 16 deletions lib/enzyme.js

This file was deleted.

Loading

0 comments on commit 353e5b8

Please sign in to comment.