import React from 'react'; import { render, RenderResult } from '@testing-library/react'; import { generateKindClassName } from './utils/generateKindClassName/generateKindClassName'; import { flattenNestedTree, LineTableRow, processToken, treeToLines, } from './renderingPlugin'; describe('processToken()', () => { test('when passed `null`, it returns `null`', () => { expect(processToken(null)).toBeNull(); expect(processToken(null, 0)).toBeNull(); expect(processToken(null, 1)).toBeNull(); }); test('when passed undefined, it returns `null`', () => { expect(processToken(undefined)).toBeNull(); expect(processToken(undefined, 0)).toBeNull(); expect(processToken(undefined, 1)).toBeNull(); }); test('when passed an Array, it returns an Array', () => { expect(processToken([])).toBeInstanceOf(Array); expect( processToken( [ { kind: 'hello', children: [] }, { kind: 'world', children: [] }, ], 0, ), ).toBeInstanceOf(Array); expect(processToken(['hello', 'hello', 'hello'], 1)).toBeInstanceOf(Array); }); test('when passed an object, it returns a React element', () => { expect( React.isValidElement(processToken({ kind: 'hello', children: [] })), ).toBeTruthy(); expect( React.isValidElement(processToken({ kind: 'hello', children: [] }, 0)), ).toBeTruthy(); expect( React.isValidElement(processToken({ kind: 'hello', children: [] }, 1)), ).toBeTruthy(); }); test('when passed a string, it returns a string', () => { expect(processToken('hello')).toBe('hello'); expect(processToken('hello', 0)).toBe('hello'); expect(processToken('hello', 1)).toBe('hello'); }); test("when processToken is passed an argument that's not explicitly supported, it returns that value", () => { // @ts-expect-error expect(processToken(false)).toBe(false); // @ts-expect-error expect(processToken(false, 0)).toBe(false); // @ts-expect-error expect(processToken(false, 1)).toBe(false); }); }); describe('LineTableRow', () => { const content = 'hello'; const lineNumber = 1; function renderLineTableRow(): RenderResult { return render( {content}
, ); } test('renders a single table row as the root node', () => { const { getByTestId } = renderLineTableRow(); // The component must return a single as the root node. // We render a table and tbody tag as parents of the element to avoid a console error, // then get the children of that tbody tag to test that the tr element is the only child. const rows = getByTestId('tbody').children; expect(rows.length).toBe(1); expect(rows[0]).toBeInTheDocument(); expect(rows[0].tagName).toBe('TR'); }); test('renders two table cells', () => { const { getAllByRole } = renderLineTableRow(); const cells = getAllByRole('cell'); expect(cells.length).toBe(2); cells.forEach(cell => expect(cell).toBeInTheDocument()); }); test(`renders the line number (${lineNumber}) in the first cell`, () => { const { getAllByRole } = renderLineTableRow(); const cells = getAllByRole('cell'); expect(cells[0]).toHaveTextContent(lineNumber.toString()); }); test(`renders '${content}' in the second cell`, () => { const { getAllByRole } = renderLineTableRow(); const cells = getAllByRole('cell'); expect(cells[1]).toHaveTextContent(content); }); }); interface TokenObject { kind: string; children: Array; } const sampleChildren: Array = [ // Line break at the beginning should be stripped. '\n', { kind: 'function', children: [ { kind: 'keyword', children: ['function'] }, ' ', { kind: 'title', children: ['greeting'] }, '(', { kind: 'params', children: ['entity'] }, ') ', ], }, '{', '\n ', { kind: 'keyword', children: ['return'] }, ' ', { kind: 'string', children: ['`Hello, ', { kind: 'subst', children: ['${entity}'] }, '!`'], }, ';\n', '}', '\n\n', { kind: 'built_in', children: ['console'] }, '.log', '(', 'greeting', '(', { kind: 'string', children: ["'World'"] }, '))', // Line break at the end should be stripped. ';\n', ]; describe('flattenNestedTree()', () => { test('when passed an array of strings, returns an array of strings', () => { const flattenedArray = flattenNestedTree(['test1', 'test2']); expect(flattenedArray instanceof Array).toBeTruthy(); expect(flattenedArray[0]).toEqual('test1'); expect(flattenedArray[1]).toEqual('test2'); }); test('when passed an array of objects, returns an array of objects with updated kind', () => { const obj1 = { children: ['obj1'], kind: 'string' }; const obj2 = { children: ['obj2'], kind: 'function' }; const [modifiedObj1, modifiedObj2] = flattenNestedTree([obj1, obj2]); expect(typeof modifiedObj1 === 'object').toBeTruthy(); expect((modifiedObj1 as any).children[0]).toEqual('obj1'); expect((modifiedObj1 as any).kind).toEqual( generateKindClassName([obj1.kind]), ); expect(typeof modifiedObj2 === 'object').toBeTruthy(); expect((modifiedObj2 as any).children[0]).toEqual('obj2'); expect((modifiedObj2 as any).kind).toEqual( generateKindClassName([obj2.kind]), ); }); test('when passed a token object, returns an array', () => { const [item1, item2] = flattenNestedTree({ kind: 'test1', children: ['hello', { kind: 'test2', children: ['world'] }], }); expect(item1).toEqual('hello'); expect((item2 as TokenObject).kind).toEqual( generateKindClassName(['test2']), ); expect((item2 as TokenObject).children[0]).toEqual('world'); expect(true).toBeTruthy(); }); test('when passed an array with a nested object, returns a flattened array of objects with updated kind', () => { expect(true).toBe(true); const tokenChildren = ['test', 'test2', 'test3']; const tokenKind = ['kind1', 'kind2', 'kind3']; const [obj1, obj2, obj3] = flattenNestedTree([ { kind: tokenKind[0], children: [ tokenChildren[0], { kind: tokenKind[1], children: [ tokenChildren[1], { kind: tokenKind[2], children: [tokenChildren[2]] }, ], }, ], }, ]); expect((obj1 as any).children[0]).toEqual(tokenChildren[0]); expect((obj1 as any).kind).toEqual(generateKindClassName([tokenKind[0]])); expect((obj2 as any).children[0]).toEqual(tokenChildren[1]); // order of classes doesn't matter expect([ generateKindClassName([tokenKind[0], tokenKind[1]]), generateKindClassName([tokenKind[1], tokenKind[0]]), ]).toContain((obj2 as any).kind); expect((obj3 as any).children[0]).toEqual(tokenChildren[2]); expect([ generateKindClassName([tokenKind[0], tokenKind[1], tokenKind[2]]), generateKindClassName([tokenKind[2], tokenKind[1], tokenKind[0]]), // add other permutations if this test is failing ]).toContain((obj3 as any).kind); }); }); describe('treeToLines()', () => { function validateLine(line: any) { function isNonArrayObject(obj: any) { if (obj instanceof Array) { return false; } return typeof obj === 'object'; } expect(line).toBeInstanceOf(Array); line.forEach((el: any) => { expect(el).toBeDefined(); expect(el).not.toBeNull(); if (typeof el === 'string') { expect(typeof el).toBe('string'); } else if (isNonArrayObject(el)) { expect(el).toHaveProperty('kind'); expect(el).toHaveProperty('children'); el.children.forEach((child: any) => { expect( typeof child === 'string' || isNonArrayObject(child), ).toBeTruthy(); }); } else { throw new TypeError( "element in array was not of types: 'object' | 'string'", ); } }); } test('when passed a valid set of children, returns a valid Array', () => { treeToLines(sampleChildren).forEach(validateLine); }); test('when passed an invalid set of children, returns a valid Array', () => { // @ts-expect-error treeToLines([...sampleChildren, null, 0]).forEach(validateLine); }); });