_setup.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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 function renderMapDoesNotContain(
  23. renderMap: RenderMap,
  24. key: string,
  25. expected: (RegExp | string)[] | RegExp | string,
  26. ) {
  27. expect(renderMap.has(key), `RenderMap is missing key "${key}".`).toBe(true);
  28. return codeDoesNotContain(renderMap.get(key), expected);
  29. }
  30. export async function codeContains(actual: string, expected: (RegExp | string)[] | RegExp | string) {
  31. const expectedArray = Array.isArray(expected) ? expected : [expected];
  32. const normalizedActual = await normalizeCode(actual);
  33. expectedArray.forEach(expectedResult => {
  34. if (typeof expectedResult === 'string') {
  35. expect(normalizedActual).toMatch(codeStringAsRegex(expectedResult));
  36. } else {
  37. expect(normalizedActual).toMatch(expectedResult);
  38. }
  39. });
  40. }
  41. export async function codeDoesNotContain(actual: string, expected: (RegExp | string)[] | RegExp | string) {
  42. const expectedArray = Array.isArray(expected) ? expected : [expected];
  43. const normalizedActual = await normalizeCode(actual);
  44. expectedArray.forEach(expectedResult => {
  45. if (typeof expectedResult === 'string') {
  46. expect(normalizedActual).not.toMatch(codeStringAsRegex(expectedResult));
  47. } else {
  48. expect(normalizedActual).not.toMatch(expectedResult);
  49. }
  50. });
  51. }
  52. export function renderMapContainsImports(renderMap: RenderMap, key: string, expectedImports: Record<string, string[]>) {
  53. expect(renderMap.has(key), `RenderMap is missing key "${key}".`).toBe(true);
  54. return codeContainsImports(renderMap.get(key), expectedImports);
  55. }
  56. export function renderMapDoesNotContainImports(
  57. renderMap: RenderMap,
  58. key: string,
  59. expectedImports: Record<string, string[]>,
  60. ) {
  61. expect(renderMap.has(key), `RenderMap is missing key "${key}".`).toBe(true);
  62. return codeDoesNotContainImports(renderMap.get(key), expectedImports);
  63. }
  64. export async function codeContainsImports(actual: string, expectedImports: Record<string, string[]>) {
  65. const normalizedActual = await inlineCode(actual);
  66. const importPairs = Object.entries(expectedImports).flatMap(([key, value]) => {
  67. return value.map(v => [key, v] as const);
  68. });
  69. importPairs.forEach(([importFrom, importValue]) => {
  70. expect(normalizedActual).toMatch(new RegExp(`import{[^}]*\\b${importValue}\\b[^}]*}from'${importFrom}'`));
  71. });
  72. }
  73. export async function codeDoesNotContainImports(actual: string, expectedImports: Record<string, string[]>) {
  74. const normalizedActual = await inlineCode(actual);
  75. const importPairs = Object.entries(expectedImports).flatMap(([key, value]) => {
  76. return value.map(v => [key, v] as const);
  77. });
  78. importPairs.forEach(([importFrom, importValue]) => {
  79. expect(normalizedActual).not.toMatch(new RegExp(`import{[^}]*\\b${importValue}\\b[^}]*}from'${importFrom}'`));
  80. });
  81. }
  82. export function codeStringAsRegex(code: string) {
  83. const stringAsRegex = escapeRegex(code)
  84. // Transform spaces between words into required whitespace.
  85. .replace(/(\w)\s+(\w)/g, '$1\\s+$2')
  86. // Do it again for single-character words — e.g. "as[ ]a[ ]token".
  87. .replace(/(\w)\s+(\w)/g, '$1\\s+$2')
  88. // Transform other spaces into optional whitespace.
  89. .replace(/\s+/g, '\\s*');
  90. return new RegExp(stringAsRegex);
  91. }
  92. async function normalizeCode(code: string) {
  93. try {
  94. code = await format(code, PRETTIER_OPTIONS);
  95. } catch {
  96. // Ignore errors.
  97. }
  98. return code.trim();
  99. }
  100. async function inlineCode(code: string) {
  101. return (await normalizeCode(code)).replace(/\s+/g, ' ').replace(/\s*(\W)\s*/g, '$1');
  102. }
  103. function escapeRegex(stringAsRegex: string) {
  104. return stringAsRegex.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
  105. }