_setup.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { RenderMap } from '@codama/renderers-core';
  2. import { Plugin } from 'prettier';
  3. import * as estreePlugin from 'prettier/plugins/estree';
  4. import * as typeScriptPlugin from 'prettier/plugins/typescript';
  5. import { format } from 'prettier/standalone';
  6. import { expect } from 'vitest';
  7. const PRETTIER_OPTIONS: Parameters<typeof format>[1] = {
  8. arrowParens: 'always',
  9. parser: 'typescript',
  10. plugins: [estreePlugin as Plugin<unknown>, typeScriptPlugin],
  11. printWidth: 80,
  12. semi: true,
  13. singleQuote: true,
  14. tabWidth: 2,
  15. trailingComma: 'none',
  16. useTabs: false,
  17. };
  18. export function renderMapContains(renderMap: RenderMap, key: string, expected: (RegExp | string)[] | RegExp | string) {
  19. expect(renderMap.has(key), `RenderMap is missing key "${key}".`).toBe(true);
  20. return codeContains(renderMap.get(key), expected);
  21. }
  22. export async function codeContains(actual: string, expected: (RegExp | string)[] | RegExp | string) {
  23. const expectedArray = Array.isArray(expected) ? expected : [expected];
  24. const normalizedActual = await normalizeCode(actual);
  25. expectedArray.forEach(expectedResult => {
  26. if (typeof expectedResult === 'string') {
  27. expect(normalizedActual).toMatch(codeStringAsRegex(expectedResult));
  28. } else {
  29. expect(normalizedActual).toMatch(expectedResult);
  30. }
  31. });
  32. }
  33. export async function codeDoesNotContain(actual: string, expected: (RegExp | string)[] | RegExp | string) {
  34. const expectedArray = Array.isArray(expected) ? expected : [expected];
  35. const normalizedActual = await normalizeCode(actual);
  36. expectedArray.forEach(expectedResult => {
  37. if (typeof expectedResult === 'string') {
  38. expect(normalizedActual).not.toMatch(codeStringAsRegex(expectedResult));
  39. } else {
  40. expect(normalizedActual).not.toMatch(expectedResult);
  41. }
  42. });
  43. }
  44. export function renderMapContainsImports(renderMap: RenderMap, key: string, expectedImports: Record<string, string[]>) {
  45. expect(renderMap.has(key), `RenderMap is missing key "${key}".`).toBe(true);
  46. return codeContainsImports(renderMap.get(key), expectedImports);
  47. }
  48. export async function codeContainsImports(actual: string, expectedImports: Record<string, string[]>) {
  49. const normalizedActual = await inlineCode(actual);
  50. const importPairs = Object.entries(expectedImports).flatMap(([key, value]) => {
  51. return value.map(v => [key, v] as const);
  52. });
  53. importPairs.forEach(([importFrom, importValue]) => {
  54. expect(normalizedActual).toMatch(new RegExp(`import{[^}]*\\b${importValue}\\b[^}]*}from'${importFrom}'`));
  55. });
  56. }
  57. export function codeStringAsRegex(code: string) {
  58. const stringAsRegex = escapeRegex(code)
  59. // Transform spaces between words into required whitespace.
  60. .replace(/(\w)\s+(\w)/g, '$1\\s+$2')
  61. // Do it again for single-character words — e.g. "as[ ]a[ ]token".
  62. .replace(/(\w)\s+(\w)/g, '$1\\s+$2')
  63. // Transform other spaces into optional whitespace.
  64. .replace(/\s+/g, '\\s*');
  65. return new RegExp(stringAsRegex);
  66. }
  67. async function normalizeCode(code: string) {
  68. try {
  69. code = await format(code, PRETTIER_OPTIONS);
  70. } catch {
  71. // Ignore errors.
  72. }
  73. return code.trim();
  74. }
  75. async function inlineCode(code: string) {
  76. return (await normalizeCode(code)).replace(/\s+/g, ' ').replace(/\s*(\W)\s*/g, '$1');
  77. }
  78. function escapeRegex(stringAsRegex: string) {
  79. return stringAsRegex.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
  80. }