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(
,
);
}
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);
});
});