• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / js-slang / 24834367427

23 Apr 2026 12:09PM UTC coverage: 78.541% (+0.2%) from 78.391%
24834367427

Pull #1893

github

web-flow
Merge ab101147d into 715603479
Pull Request #1893: Error Handling and Stringify Changes

3126 of 4197 branches covered (74.48%)

Branch coverage included in aggregate %.

801 of 975 new or added lines in 76 files covered. (82.15%)

20 existing lines in 11 files now uncovered.

7056 of 8767 relevant lines covered (80.48%)

173930.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.23
/src/utils/testing/index.ts
1
import { type Assertion, expect, vi } from 'vitest';
2
import { parseError, runInContext } from '../..';
3
import createContext, { defineBuiltin } from '../../createContext';
4
import { Chapter } from '../../langs';
5
import type { CustomBuiltIns } from '../../types';
6
import { assertIsFinished, processTestOptions } from './misc';
7
import { mockContext } from './mocks';
8
import type { TestContext, TestOptions, TestResults } from './types';
9

10
/**
11
 * The way Vitest works means that it doesn't let us call inline snapshot matchers via a 'proxy' function.
12
 * You have to call the matchers directly from `expect`. So, we remove the type here to prevent
13
 * usage of these matchers with the expect functions below.
14
 */
15
type RemoveMatcher<T extends object> = Omit<
16
  T,
17
  'toMatchInlineSnapshot' | 'toThrowErrorMatchingInlineSnapshot'
18
>;
19
function removeMatcher<T extends object>(obj: T): RemoveMatcher<T> {
20
  return obj;
519✔
21
}
22

23
// Copied @vitest/expect/dist/index.d.ts
24
type Promisify<O> = {
25
  [K in keyof O]: O[K] extends (...args: infer A) => infer R
26
    ? Promisify<O[K]> & ((...args: A) => Promise<R>)
27
    : O[K];
28
};
29

30
export function createTestContext(rawOptions: TestOptions = {}): TestContext {
735✔
31
  const { chapter, variant, testBuiltins, languageOptions }: Exclude<TestOptions, Chapter> =
32
    typeof rawOptions === 'number'
735✔
33
      ? {
34
          chapter: rawOptions,
35
        }
36
      : rawOptions;
37

38
  const otherTestResults: TestResults = {
735✔
39
    displayResult: [],
40
    promptResult: [],
41
    alertResult: [],
42
    visualiseListResult: [],
43
  };
44

45
  const customBuiltIns: CustomBuiltIns = {
735✔
46
    rawDisplay:
47
      testBuiltins?.rawDisplay ??
772✔
48
      ((str1, str2, _externalContext) => {
49
        otherTestResults.displayResult.push((str2 === undefined ? '' : str2 + ' ') + str1);
37✔
50
        return str1;
37✔
51
      }),
52
    prompt:
53
      testBuiltins?.prompt ??
735!
54
      ((str, _externalContext) => {
NEW
55
        otherTestResults.promptResult.push(str);
×
NEW
56
        return null;
×
57
      }),
58
    alert:
59
      testBuiltins?.alert ??
737✔
60
      ((str, _externalContext) => {
61
        otherTestResults.alertResult.push(str);
2✔
62
      }),
63
    visualiseList:
64
      testBuiltins?.visualiseList ??
738✔
65
      (value => {
66
        otherTestResults.visualiseListResult.push(value);
3✔
67
      }),
68
  };
69

70
  const evalContext = createContext(
735✔
71
    chapter,
72
    variant,
73
    languageOptions,
74
    [],
75
    undefined,
76
    customBuiltIns,
77
  );
78
  Object.entries(testBuiltins ?? {}).forEach(([key, value]) =>
735✔
79
    defineBuiltin(evalContext, key, value),
80
  );
81

82
  return {
735✔
83
    ...evalContext,
84
    ...otherTestResults,
85
  };
86
}
87

88
export async function testInContext(code: string, rawOptions: TestOptions) {
89
  const options = processTestOptions(rawOptions);
706✔
90
  const context = createTestContext(options);
706✔
91
  const result = await runInContext(code, context);
706✔
92
  return {
706✔
93
    context,
94
    result,
95
  };
96
}
97

98
/**
99
 * Run the given code and expect it to finish without errors
100
 * @returns Context and result of test
101
 */
102
export async function testSuccess(code: string, options: TestOptions = {}) {
67✔
103
  const { context, result } = await testInContext(code, options);
67✔
104
  if (result.status !== 'finished') {
67✔
105
    console.log(context.errors);
1✔
106
  }
107

108
  assertIsFinished(result);
67✔
109
  return {
67✔
110
    context,
111
    result,
112
  };
113
}
114

115
/**
116
 * Run the given code and expect it to finish with errors
117
 * @returns String value of parsed errors
118
 */
119
export async function testFailure(code: string, options: TestOptions = {}) {
129✔
120
  const res = await testInContext(code, options);
129✔
121
  expect(res.result.status).toEqual('error');
129✔
122
  return parseError(res.context.errors);
129✔
123
}
124

125
/**
126
 * Run the given code and expect it to finish without errors. Use
127
 * as if using `expect()`
128
 */
129
export const expectFinishedResult = vi.defineHelper(
28✔
130
  (code: string, options: TestOptions = {}): RemoveMatcher<Promisify<Assertion<Promise<any>>>> => {
351✔
131
    return removeMatcher(
351✔
132
      expect(
133
        testInContext(code, options).then(({ result, context }) => {
134
          if (result.status === 'error') {
351✔
135
            const errStr = parseError(context.errors);
1✔
136
            console.log(errStr);
1✔
137
          }
138
          assertIsFinished(result);
351✔
139
          return result.value;
351✔
140
        }),
141
      ).resolves,
142
    );
143
  },
144
);
145

146
/**
147
 * Expect the code to error, then test the parsed error value. Use as if using
148
 * `expect`
149
 */
150
export const expectParsedError = (
28✔
151
  code: string,
152
  options: TestOptions = {},
159✔
153
  verbose?: boolean,
154
): RemoveMatcher<Promisify<Assertion<Promise<string>>>> => {
155
  return removeMatcher(
159✔
156
    expect(
157
      testInContext(code, options).then(({ result, context }) => {
158
        expect(result.status).toEqual('error');
159✔
159
        return parseError(context.errors, verbose);
159✔
160
      }),
161
    ).resolves,
162
  );
163
};
164

165
export const expectNativeToTimeoutAndError = vi.defineHelper(
28✔
166
  async (code: string, timeout: number) => {
167
    const start = Date.now();
7✔
168
    const context = mockContext(Chapter.SOURCE_4);
7✔
169
    await runInContext(code, context, {
7✔
170
      executionMethod: 'native',
171
      throwInfiniteLoops: false,
172
    });
173
    const timeTaken = Date.now() - start;
7✔
174
    expect(timeTaken).toBeLessThan(timeout * 5);
7✔
175
    expect(timeTaken).toBeGreaterThanOrEqual(timeout);
7✔
176
    return parseError(context.errors);
7✔
177
  },
178
);
179

180
/**
181
 * Run the given code, expect it to finish without errors and also match a snapshot
182
 */
183
export const snapshotSuccess = vi.defineHelper(
28✔
184
  async (code: string, options: TestOptions = {}, name?: string) => {
×
NEW
185
    const results = await testSuccess(code, options);
×
NEW
186
    if (name === undefined) {
×
NEW
187
      expect(results).toMatchSnapshot();
×
188
    } else {
NEW
189
      expect(results).toMatchSnapshot(name);
×
190
    }
NEW
191
    return results;
×
192
  },
193
);
194

195
/**
196
 * Run the given code, expect it to finish with errors and that those errors match a snapshot
197
 */
198
export const snapshotFailure = vi.defineHelper(
28✔
199
  async (code: string, options: TestOptions = {}, name?: string) => {
12✔
200
    const results = await testFailure(code, options);
12✔
201
    if (name === undefined) {
12!
202
      expect(results).toMatchSnapshot();
12✔
203
    } else {
NEW
204
      expect(results).toMatchSnapshot(name);
×
205
    }
206
    return results;
12✔
207
  },
208
);
209

210
export function expectDisplayResult(
211
  code: string,
212
  options: TestOptions = {},
9✔
213
): RemoveMatcher<Promisify<Assertion<Promise<string[]>>>> {
214
  return removeMatcher(
9✔
215
    expect(testSuccess(code, options).then(({ context: { displayResult } }) => displayResult))
9✔
216
      .resolves,
217
  );
218
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc