@vibe-lang/runtime 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +46 -0
- package/src/ast/index.ts +375 -0
- package/src/ast.ts +2 -0
- package/src/debug/advanced-features.ts +482 -0
- package/src/debug/bun-inspector.ts +424 -0
- package/src/debug/handoff-manager.ts +283 -0
- package/src/debug/index.ts +150 -0
- package/src/debug/runner.ts +365 -0
- package/src/debug/server.ts +565 -0
- package/src/debug/stack-merger.ts +267 -0
- package/src/debug/state.ts +581 -0
- package/src/debug/test/advanced-features.test.ts +300 -0
- package/src/debug/test/e2e.test.ts +218 -0
- package/src/debug/test/handoff-manager.test.ts +256 -0
- package/src/debug/test/runner.test.ts +256 -0
- package/src/debug/test/stack-merger.test.ts +163 -0
- package/src/debug/test/state.test.ts +400 -0
- package/src/debug/test/ts-debug-integration.test.ts +374 -0
- package/src/debug/test/ts-import-tracker.test.ts +125 -0
- package/src/debug/test/ts-source-map.test.ts +169 -0
- package/src/debug/ts-import-tracker.ts +151 -0
- package/src/debug/ts-source-map.ts +171 -0
- package/src/errors/index.ts +124 -0
- package/src/index.ts +358 -0
- package/src/lexer/index.ts +348 -0
- package/src/lexer.ts +2 -0
- package/src/parser/index.ts +792 -0
- package/src/parser/parse.ts +45 -0
- package/src/parser/test/async.test.ts +248 -0
- package/src/parser/test/destructuring.test.ts +167 -0
- package/src/parser/test/do-expression.test.ts +486 -0
- package/src/parser/test/errors/do-expression.test.ts +95 -0
- package/src/parser/test/errors/error-locations.test.ts +230 -0
- package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
- package/src/parser/test/errors/missing-tokens.test.ts +126 -0
- package/src/parser/test/errors/model-declaration.test.ts +185 -0
- package/src/parser/test/errors/nested-blocks.test.ts +226 -0
- package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
- package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
- package/src/parser/test/import-export.test.ts +143 -0
- package/src/parser/test/literals.test.ts +404 -0
- package/src/parser/test/model-declaration.test.ts +161 -0
- package/src/parser/test/nested-blocks.test.ts +402 -0
- package/src/parser/test/parser.test.ts +743 -0
- package/src/parser/test/private.test.ts +136 -0
- package/src/parser/test/template-literal.test.ts +127 -0
- package/src/parser/test/tool-declaration.test.ts +302 -0
- package/src/parser/test/ts-block.test.ts +252 -0
- package/src/parser/test/type-annotations.test.ts +254 -0
- package/src/parser/visitor/helpers.ts +330 -0
- package/src/parser/visitor.ts +794 -0
- package/src/parser.ts +2 -0
- package/src/runtime/ai/cache-chunking.test.ts +69 -0
- package/src/runtime/ai/cache-chunking.ts +73 -0
- package/src/runtime/ai/client.ts +109 -0
- package/src/runtime/ai/context.ts +168 -0
- package/src/runtime/ai/formatters.ts +316 -0
- package/src/runtime/ai/index.ts +38 -0
- package/src/runtime/ai/language-ref.ts +38 -0
- package/src/runtime/ai/providers/anthropic.ts +253 -0
- package/src/runtime/ai/providers/google.ts +201 -0
- package/src/runtime/ai/providers/openai.ts +156 -0
- package/src/runtime/ai/retry.ts +100 -0
- package/src/runtime/ai/return-tools.ts +301 -0
- package/src/runtime/ai/test/client.test.ts +83 -0
- package/src/runtime/ai/test/formatters.test.ts +485 -0
- package/src/runtime/ai/test/retry.test.ts +137 -0
- package/src/runtime/ai/test/return-tools.test.ts +450 -0
- package/src/runtime/ai/test/tool-loop.test.ts +319 -0
- package/src/runtime/ai/test/tool-schema.test.ts +241 -0
- package/src/runtime/ai/tool-loop.ts +203 -0
- package/src/runtime/ai/tool-schema.ts +151 -0
- package/src/runtime/ai/types.ts +113 -0
- package/src/runtime/ai-logger.ts +255 -0
- package/src/runtime/ai-provider.ts +347 -0
- package/src/runtime/async/dependencies.ts +276 -0
- package/src/runtime/async/executor.ts +293 -0
- package/src/runtime/async/index.ts +43 -0
- package/src/runtime/async/scheduling.ts +163 -0
- package/src/runtime/async/test/dependencies.test.ts +284 -0
- package/src/runtime/async/test/executor.test.ts +388 -0
- package/src/runtime/context.ts +357 -0
- package/src/runtime/exec/ai.ts +139 -0
- package/src/runtime/exec/expressions.ts +475 -0
- package/src/runtime/exec/frames.ts +26 -0
- package/src/runtime/exec/functions.ts +305 -0
- package/src/runtime/exec/interpolation.ts +312 -0
- package/src/runtime/exec/statements.ts +604 -0
- package/src/runtime/exec/tools.ts +129 -0
- package/src/runtime/exec/typescript.ts +215 -0
- package/src/runtime/exec/variables.ts +279 -0
- package/src/runtime/index.ts +975 -0
- package/src/runtime/modules.ts +452 -0
- package/src/runtime/serialize.ts +103 -0
- package/src/runtime/state.ts +489 -0
- package/src/runtime/stdlib/core.ts +45 -0
- package/src/runtime/stdlib/directory.test.ts +156 -0
- package/src/runtime/stdlib/edit.test.ts +154 -0
- package/src/runtime/stdlib/fastEdit.test.ts +201 -0
- package/src/runtime/stdlib/glob.test.ts +106 -0
- package/src/runtime/stdlib/grep.test.ts +144 -0
- package/src/runtime/stdlib/index.ts +16 -0
- package/src/runtime/stdlib/readFile.test.ts +123 -0
- package/src/runtime/stdlib/tools/index.ts +707 -0
- package/src/runtime/stdlib/writeFile.test.ts +157 -0
- package/src/runtime/step.ts +969 -0
- package/src/runtime/test/ai-context.test.ts +1086 -0
- package/src/runtime/test/ai-result-object.test.ts +419 -0
- package/src/runtime/test/ai-tool-flow.test.ts +859 -0
- package/src/runtime/test/async-execution-order.test.ts +618 -0
- package/src/runtime/test/async-execution.test.ts +344 -0
- package/src/runtime/test/async-nested.test.ts +660 -0
- package/src/runtime/test/async-parallel-timing.test.ts +546 -0
- package/src/runtime/test/basic1.test.ts +154 -0
- package/src/runtime/test/binary-operators.test.ts +431 -0
- package/src/runtime/test/break-statement.test.ts +257 -0
- package/src/runtime/test/context-modes.test.ts +650 -0
- package/src/runtime/test/context.test.ts +466 -0
- package/src/runtime/test/core-functions.test.ts +228 -0
- package/src/runtime/test/e2e.test.ts +88 -0
- package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
- package/src/runtime/test/error-locations/main-error.vibe +4 -0
- package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
- package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
- package/src/runtime/test/for-in.test.ts +312 -0
- package/src/runtime/test/helpers.ts +69 -0
- package/src/runtime/test/imports.test.ts +334 -0
- package/src/runtime/test/json-expressions.test.ts +232 -0
- package/src/runtime/test/literals.test.ts +372 -0
- package/src/runtime/test/logical-indexing.test.ts +478 -0
- package/src/runtime/test/member-methods.test.ts +324 -0
- package/src/runtime/test/model-config.test.ts +338 -0
- package/src/runtime/test/null-handling.test.ts +342 -0
- package/src/runtime/test/private-visibility.test.ts +332 -0
- package/src/runtime/test/runtime-state.test.ts +514 -0
- package/src/runtime/test/scoping.test.ts +370 -0
- package/src/runtime/test/string-interpolation.test.ts +354 -0
- package/src/runtime/test/template-literal.test.ts +181 -0
- package/src/runtime/test/tool-execution.test.ts +467 -0
- package/src/runtime/test/tool-schema-generation.test.ts +477 -0
- package/src/runtime/test/tostring.test.ts +210 -0
- package/src/runtime/test/ts-block.test.ts +594 -0
- package/src/runtime/test/ts-error-location.test.ts +231 -0
- package/src/runtime/test/types.test.ts +732 -0
- package/src/runtime/test/verbose-logger.test.ts +710 -0
- package/src/runtime/test/vibe-expression.test.ts +54 -0
- package/src/runtime/test/vibe-value-errors.test.ts +541 -0
- package/src/runtime/test/while.test.ts +232 -0
- package/src/runtime/tools/builtin.ts +30 -0
- package/src/runtime/tools/directory-tools.ts +70 -0
- package/src/runtime/tools/file-tools.ts +228 -0
- package/src/runtime/tools/index.ts +5 -0
- package/src/runtime/tools/registry.ts +48 -0
- package/src/runtime/tools/search-tools.ts +134 -0
- package/src/runtime/tools/security.ts +36 -0
- package/src/runtime/tools/system-tools.ts +312 -0
- package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
- package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
- package/src/runtime/tools/test/registry.test.ts +713 -0
- package/src/runtime/tools/test/security.test.ts +86 -0
- package/src/runtime/tools/test/system-tools.test.ts +679 -0
- package/src/runtime/tools/test/ts-schema.test.ts +357 -0
- package/src/runtime/tools/ts-schema.ts +341 -0
- package/src/runtime/tools/types.ts +89 -0
- package/src/runtime/tools/utility-tools.ts +198 -0
- package/src/runtime/ts-eval.ts +126 -0
- package/src/runtime/types.ts +797 -0
- package/src/runtime/validation.ts +160 -0
- package/src/runtime/verbose-logger.ts +459 -0
- package/src/runtime.ts +2 -0
- package/src/semantic/analyzer-context.ts +62 -0
- package/src/semantic/analyzer-validators.ts +575 -0
- package/src/semantic/analyzer-visitors.ts +534 -0
- package/src/semantic/analyzer.ts +83 -0
- package/src/semantic/index.ts +11 -0
- package/src/semantic/symbol-table.ts +58 -0
- package/src/semantic/test/async-validation.test.ts +301 -0
- package/src/semantic/test/compress-validation.test.ts +179 -0
- package/src/semantic/test/const-reassignment.test.ts +111 -0
- package/src/semantic/test/control-flow.test.ts +346 -0
- package/src/semantic/test/destructuring.test.ts +185 -0
- package/src/semantic/test/duplicate-declarations.test.ts +168 -0
- package/src/semantic/test/export-validation.test.ts +111 -0
- package/src/semantic/test/fixtures/math.ts +31 -0
- package/src/semantic/test/imports.test.ts +148 -0
- package/src/semantic/test/json-type.test.ts +68 -0
- package/src/semantic/test/literals.test.ts +127 -0
- package/src/semantic/test/model-validation.test.ts +179 -0
- package/src/semantic/test/prompt-validation.test.ts +343 -0
- package/src/semantic/test/scoping.test.ts +312 -0
- package/src/semantic/test/tool-validation.test.ts +306 -0
- package/src/semantic/test/ts-type-checking.test.ts +563 -0
- package/src/semantic/test/type-constraints.test.ts +111 -0
- package/src/semantic/test/type-inference.test.ts +87 -0
- package/src/semantic/test/type-validation.test.ts +552 -0
- package/src/semantic/test/undefined-variables.test.ts +163 -0
- package/src/semantic/ts-block-checker.ts +204 -0
- package/src/semantic/ts-signatures.ts +194 -0
- package/src/semantic/ts-types.ts +170 -0
- package/src/semantic/types.ts +58 -0
- package/tests/fixtures/conditional-logic.vibe +14 -0
- package/tests/fixtures/function-call.vibe +16 -0
- package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
- package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
- package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
- package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
- package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
- package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
- package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
- package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
- package/tests/fixtures/imports/nested-import/main.vibe +3 -0
- package/tests/fixtures/imports/nested-import/utils.ts +3 -0
- package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
- package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
- package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
- package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
- package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
- package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
- package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
- package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
- package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
- package/tests/fixtures/imports/ts-import/main.vibe +4 -0
- package/tests/fixtures/imports/ts-import/math.ts +9 -0
- package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/data.ts +10 -0
- package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
- package/tests/fixtures/multiple-ai-calls.vibe +10 -0
- package/tests/fixtures/simple-greeting.vibe +6 -0
- package/tests/fixtures/template-literals.vibe +11 -0
- package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
- package/tests/integration/basic-ai/basic-ai.vibe +12 -0
- package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
- package/tests/integration/bug-fix/buggy-code.ts +22 -0
- package/tests/integration/bug-fix/fix-bug.vibe +21 -0
- package/tests/integration/compress/compress.integration.test.ts +206 -0
- package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
- package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
- package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
- package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
- package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
- package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
- package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
- package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test';
|
|
2
|
+
import { parse } from '../../parser/parse';
|
|
3
|
+
import { SemanticAnalyzer } from '../../semantic/analyzer';
|
|
4
|
+
import { createInitialState, resumeWithTsResult } from '../state';
|
|
5
|
+
import { runUntilPause } from '../step';
|
|
6
|
+
import { isVibeValue } from '../types';
|
|
7
|
+
|
|
8
|
+
const analyzer = new SemanticAnalyzer();
|
|
9
|
+
|
|
10
|
+
function getErrors(source: string): string[] {
|
|
11
|
+
const ast = parse(source);
|
|
12
|
+
return analyzer.analyze(ast, source).map((e) => e.message);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('Null Handling', () => {
|
|
16
|
+
describe('null literal parsing', () => {
|
|
17
|
+
it('parses null as a literal value', () => {
|
|
18
|
+
const ast = parse('let x: text = null');
|
|
19
|
+
expect(ast.body[0].type).toBe('LetDeclaration');
|
|
20
|
+
const decl = ast.body[0] as { initializer: { type: string } };
|
|
21
|
+
expect(decl.initializer.type).toBe('NullLiteral');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('parses null in expressions', () => {
|
|
25
|
+
const ast = parse('let x: text = null\nlet y = x == null');
|
|
26
|
+
expect(ast.body.length).toBe(2);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('semantic validation for null assignments', () => {
|
|
31
|
+
it('rejects const with null initializer', () => {
|
|
32
|
+
const errors = getErrors('const x = null');
|
|
33
|
+
expect(errors.some(e => e.includes('Cannot initialize const with null'))).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('rejects const with typed null initializer', () => {
|
|
37
|
+
const errors = getErrors('const x: text = null');
|
|
38
|
+
expect(errors.some(e => e.includes('Cannot initialize const with null'))).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('rejects let without type annotation when initialized with null', () => {
|
|
42
|
+
const errors = getErrors('let x = null');
|
|
43
|
+
expect(errors.some(e => e.includes('Cannot infer type from null'))).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('accepts let with type annotation initialized with null', () => {
|
|
47
|
+
const errors = getErrors('let x: text = null');
|
|
48
|
+
expect(errors.filter(e => e.includes('null'))).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('accepts let with json type initialized with null', () => {
|
|
52
|
+
const errors = getErrors('let x: json = null');
|
|
53
|
+
expect(errors.filter(e => e.includes('null'))).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('null in runtime operations', () => {
|
|
58
|
+
it('evaluates null literal to null', () => {
|
|
59
|
+
const ast = parse('let x: text = null');
|
|
60
|
+
let state = createInitialState(ast);
|
|
61
|
+
state = runUntilPause(state);
|
|
62
|
+
|
|
63
|
+
expect(state.status).toBe('completed');
|
|
64
|
+
expect(state.callStack[0].locals['x'].value).toBe(null);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('allows reassigning typed variable to null', () => {
|
|
68
|
+
const ast = parse(`
|
|
69
|
+
let x: text = "hello"
|
|
70
|
+
x = null
|
|
71
|
+
`);
|
|
72
|
+
let state = createInitialState(ast);
|
|
73
|
+
state = runUntilPause(state);
|
|
74
|
+
|
|
75
|
+
expect(state.status).toBe('completed');
|
|
76
|
+
expect(state.callStack[0].locals['x'].value).toBe(null);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('compares null with == correctly', () => {
|
|
80
|
+
const ast = parse(`
|
|
81
|
+
let x: text = null
|
|
82
|
+
let isNull = x == null
|
|
83
|
+
let isNotNull = x != null
|
|
84
|
+
`);
|
|
85
|
+
let state = createInitialState(ast);
|
|
86
|
+
state = runUntilPause(state);
|
|
87
|
+
|
|
88
|
+
expect(state.status).toBe('completed');
|
|
89
|
+
expect(state.callStack[0].locals['isNull'].value).toBe(true);
|
|
90
|
+
expect(state.callStack[0].locals['isNotNull'].value).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('string concatenation with null coerces null to empty string', () => {
|
|
94
|
+
const ast = parse(`
|
|
95
|
+
let x: text = null
|
|
96
|
+
let result1 = "hello " + x
|
|
97
|
+
let result2 = x + " world"
|
|
98
|
+
`);
|
|
99
|
+
let state = createInitialState(ast);
|
|
100
|
+
state = runUntilPause(state);
|
|
101
|
+
|
|
102
|
+
expect(state.status).toBe('completed');
|
|
103
|
+
expect(state.callStack[0].locals['result1'].value).toBe('hello ');
|
|
104
|
+
expect(state.callStack[0].locals['result2'].value).toBe(' world');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('arithmetic with null creates error VibeValue', () => {
|
|
108
|
+
const ast = parse(`
|
|
109
|
+
let x: number = null
|
|
110
|
+
let result = x + 5
|
|
111
|
+
`);
|
|
112
|
+
let state = createInitialState(ast);
|
|
113
|
+
state = runUntilPause(state);
|
|
114
|
+
|
|
115
|
+
expect(state.status).toBe('completed');
|
|
116
|
+
const result = state.callStack[0].locals['result'];
|
|
117
|
+
expect(isVibeValue(result)).toBe(true);
|
|
118
|
+
expect(result.err).toBe(true); // err is now boolean
|
|
119
|
+
expect(result.errDetails?.message).toContain('null');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('subtraction with null creates error', () => {
|
|
123
|
+
const ast = parse(`
|
|
124
|
+
let x: number = null
|
|
125
|
+
let result = 10 - x
|
|
126
|
+
`);
|
|
127
|
+
let state = createInitialState(ast);
|
|
128
|
+
state = runUntilPause(state);
|
|
129
|
+
|
|
130
|
+
expect(state.status).toBe('completed');
|
|
131
|
+
const result = state.callStack[0].locals['result'];
|
|
132
|
+
expect(result.err).toBe(true); // err is now boolean
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('multiplication with null creates error', () => {
|
|
136
|
+
const ast = parse(`
|
|
137
|
+
let x: number = null
|
|
138
|
+
let result = x * 5
|
|
139
|
+
`);
|
|
140
|
+
let state = createInitialState(ast);
|
|
141
|
+
state = runUntilPause(state);
|
|
142
|
+
|
|
143
|
+
expect(state.status).toBe('completed');
|
|
144
|
+
const result = state.callStack[0].locals['result'];
|
|
145
|
+
expect(result.err).toBe(true); // err is now boolean
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('unary minus on null creates error', () => {
|
|
149
|
+
const ast = parse(`
|
|
150
|
+
let x: number = null
|
|
151
|
+
let result = -x
|
|
152
|
+
`);
|
|
153
|
+
let state = createInitialState(ast);
|
|
154
|
+
state = runUntilPause(state);
|
|
155
|
+
|
|
156
|
+
expect(state.status).toBe('completed');
|
|
157
|
+
const result = state.callStack[0].locals['result'];
|
|
158
|
+
expect(result.err).toBe(true); // err is now boolean
|
|
159
|
+
expect(result.errDetails?.message).toContain('null');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('logical operators treat null as falsy', () => {
|
|
163
|
+
const ast = parse(`
|
|
164
|
+
let x: text = null
|
|
165
|
+
let andResult = x and true
|
|
166
|
+
let orResult = x or "default"
|
|
167
|
+
`);
|
|
168
|
+
let state = createInitialState(ast);
|
|
169
|
+
state = runUntilPause(state);
|
|
170
|
+
|
|
171
|
+
expect(state.status).toBe('completed');
|
|
172
|
+
expect(state.callStack[0].locals['andResult'].value).toBe(false);
|
|
173
|
+
expect(state.callStack[0].locals['orResult'].value).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('error propagation through expressions (docs example)', () => {
|
|
178
|
+
it('error propagates through a + b when a has error', () => {
|
|
179
|
+
// Matches the docs example: let sum = a + b where a might fail
|
|
180
|
+
const ast = parse(`
|
|
181
|
+
let a: number = null
|
|
182
|
+
let b = 10
|
|
183
|
+
let sum = a + b
|
|
184
|
+
`);
|
|
185
|
+
let state = createInitialState(ast);
|
|
186
|
+
state = runUntilPause(state);
|
|
187
|
+
|
|
188
|
+
expect(state.status).toBe('completed');
|
|
189
|
+
// sum should have the error from a
|
|
190
|
+
const sum = state.callStack[0].locals['sum'];
|
|
191
|
+
expect(isVibeValue(sum)).toBe(true);
|
|
192
|
+
expect(sum.err).toBe(true);
|
|
193
|
+
expect(sum.errDetails?.message).toContain('null');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('error propagates through a + b when b has error', () => {
|
|
197
|
+
const ast = parse(`
|
|
198
|
+
let a = 10
|
|
199
|
+
let b: number = null
|
|
200
|
+
let sum = a + b
|
|
201
|
+
`);
|
|
202
|
+
let state = createInitialState(ast);
|
|
203
|
+
state = runUntilPause(state);
|
|
204
|
+
|
|
205
|
+
expect(state.status).toBe('completed');
|
|
206
|
+
const sum = state.callStack[0].locals['sum'];
|
|
207
|
+
expect(sum.err).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('first error wins when both operands have errors', () => {
|
|
211
|
+
const ast = parse(`
|
|
212
|
+
let a: number = null
|
|
213
|
+
let b: number = null
|
|
214
|
+
let sum = a + b
|
|
215
|
+
`);
|
|
216
|
+
let state = createInitialState(ast);
|
|
217
|
+
state = runUntilPause(state);
|
|
218
|
+
|
|
219
|
+
expect(state.status).toBe('completed');
|
|
220
|
+
const sum = state.callStack[0].locals['sum'];
|
|
221
|
+
expect(sum.err).toBe(true);
|
|
222
|
+
// Should contain error from left operand
|
|
223
|
+
expect(sum.errDetails?.message).toContain('null');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('error propagates through chain of operations', () => {
|
|
227
|
+
// Docs example: errors flow through naturally
|
|
228
|
+
const ast = parse(`
|
|
229
|
+
let a: number = null
|
|
230
|
+
let b = 10
|
|
231
|
+
let sum = a + b
|
|
232
|
+
let doubled = sum * 2
|
|
233
|
+
`);
|
|
234
|
+
let state = createInitialState(ast);
|
|
235
|
+
state = runUntilPause(state);
|
|
236
|
+
|
|
237
|
+
expect(state.status).toBe('completed');
|
|
238
|
+
// doubled should have the propagated error
|
|
239
|
+
const doubled = state.callStack[0].locals['doubled'];
|
|
240
|
+
expect(doubled.err).toBe(true);
|
|
241
|
+
expect(doubled.errDetails?.message).toContain('null');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('.err can be checked on final result of chain', () => {
|
|
245
|
+
const ast = parse(`
|
|
246
|
+
let a: number = null
|
|
247
|
+
let b = 10
|
|
248
|
+
let sum = a + b
|
|
249
|
+
let doubled = sum * 2
|
|
250
|
+
let hasError = doubled.err
|
|
251
|
+
`);
|
|
252
|
+
let state = createInitialState(ast);
|
|
253
|
+
state = runUntilPause(state);
|
|
254
|
+
|
|
255
|
+
expect(state.status).toBe('completed');
|
|
256
|
+
expect(state.callStack[0].locals['hasError'].value).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('no error when all values are valid', () => {
|
|
260
|
+
const ast = parse(`
|
|
261
|
+
let a = 5
|
|
262
|
+
let b = 10
|
|
263
|
+
let sum = a + b
|
|
264
|
+
let doubled = sum * 2
|
|
265
|
+
let hasError = doubled.err
|
|
266
|
+
`);
|
|
267
|
+
let state = createInitialState(ast);
|
|
268
|
+
state = runUntilPause(state);
|
|
269
|
+
|
|
270
|
+
expect(state.status).toBe('completed');
|
|
271
|
+
expect(state.callStack[0].locals['doubled'].value).toBe(30);
|
|
272
|
+
expect(state.callStack[0].locals['hasError'].value).toBe(false);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('error preserved when assigned to another variable', () => {
|
|
276
|
+
const ast = parse(`
|
|
277
|
+
let a: number = null
|
|
278
|
+
let b = a + 5
|
|
279
|
+
let c = b
|
|
280
|
+
let hasError = c.err
|
|
281
|
+
`);
|
|
282
|
+
let state = createInitialState(ast);
|
|
283
|
+
state = runUntilPause(state);
|
|
284
|
+
|
|
285
|
+
expect(state.status).toBe('completed');
|
|
286
|
+
const c = state.callStack[0].locals['c'];
|
|
287
|
+
expect(c.err).toBe(true);
|
|
288
|
+
expect(state.callStack[0].locals['hasError'].value).toBe(true);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('JS interop - undefined to null normalization', () => {
|
|
293
|
+
it('normalizes ts block undefined return to null', () => {
|
|
294
|
+
const ast = parse(`
|
|
295
|
+
let result = ts() { return undefined }
|
|
296
|
+
`);
|
|
297
|
+
let state = createInitialState(ast);
|
|
298
|
+
state = runUntilPause(state);
|
|
299
|
+
|
|
300
|
+
expect(state.status).toBe('awaiting_ts');
|
|
301
|
+
|
|
302
|
+
// Resume with undefined from TS
|
|
303
|
+
state = resumeWithTsResult(state, undefined);
|
|
304
|
+
state = runUntilPause(state);
|
|
305
|
+
|
|
306
|
+
expect(state.status).toBe('completed');
|
|
307
|
+
expect(state.callStack[0].locals['result'].value).toBe(null);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('preserves actual null from ts block', () => {
|
|
311
|
+
const ast = parse(`
|
|
312
|
+
let result = ts() { return null }
|
|
313
|
+
`);
|
|
314
|
+
let state = createInitialState(ast);
|
|
315
|
+
state = runUntilPause(state);
|
|
316
|
+
|
|
317
|
+
expect(state.status).toBe('awaiting_ts');
|
|
318
|
+
|
|
319
|
+
state = resumeWithTsResult(state, null);
|
|
320
|
+
state = runUntilPause(state);
|
|
321
|
+
|
|
322
|
+
expect(state.status).toBe('completed');
|
|
323
|
+
expect(state.callStack[0].locals['result'].value).toBe(null);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('preserves other values from ts block', () => {
|
|
327
|
+
const ast = parse(`
|
|
328
|
+
let result = ts() { return 42 }
|
|
329
|
+
`);
|
|
330
|
+
let state = createInitialState(ast);
|
|
331
|
+
state = runUntilPause(state);
|
|
332
|
+
|
|
333
|
+
expect(state.status).toBe('awaiting_ts');
|
|
334
|
+
|
|
335
|
+
state = resumeWithTsResult(state, 42);
|
|
336
|
+
state = runUntilPause(state);
|
|
337
|
+
|
|
338
|
+
expect(state.status).toBe('completed');
|
|
339
|
+
expect(state.callStack[0].locals['result'].value).toBe(42);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test';
|
|
2
|
+
import { parse } from '../../parser/parse';
|
|
3
|
+
import { createInitialState, currentFrame } from '../state';
|
|
4
|
+
import { step } from '../step';
|
|
5
|
+
import { buildLocalContext, buildGlobalContext, formatContextForAI } from '../context';
|
|
6
|
+
|
|
7
|
+
describe('private variable visibility', () => {
|
|
8
|
+
describe('runtime state', () => {
|
|
9
|
+
it('stores isPrivate flag in VibeValue', () => {
|
|
10
|
+
const code = 'let private secret: text = "hidden"';
|
|
11
|
+
const ast = parse(code);
|
|
12
|
+
let state = createInitialState(ast);
|
|
13
|
+
|
|
14
|
+
// Execute until complete
|
|
15
|
+
while (state.status === 'running') {
|
|
16
|
+
state = step(state);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const frame = currentFrame(state);
|
|
20
|
+
expect(frame.locals['secret']).toBeDefined();
|
|
21
|
+
expect(frame.locals['secret'].value).toBe('hidden');
|
|
22
|
+
expect(frame.locals['secret'].isPrivate).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('stores isPrivate flag in orderedEntries', () => {
|
|
26
|
+
const code = 'let private secret: text = "hidden"';
|
|
27
|
+
const ast = parse(code);
|
|
28
|
+
let state = createInitialState(ast);
|
|
29
|
+
|
|
30
|
+
while (state.status === 'running') {
|
|
31
|
+
state = step(state);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const frame = currentFrame(state);
|
|
35
|
+
const entry = frame.orderedEntries.find(
|
|
36
|
+
(e) => e.kind === 'variable' && e.name === 'secret'
|
|
37
|
+
);
|
|
38
|
+
expect(entry).toBeDefined();
|
|
39
|
+
expect(entry?.kind).toBe('variable');
|
|
40
|
+
if (entry?.kind === 'variable') {
|
|
41
|
+
expect(entry.isPrivate).toBe(true);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('public variables do not have isPrivate flag', () => {
|
|
46
|
+
const code = 'let visible: text = "shown"';
|
|
47
|
+
const ast = parse(code);
|
|
48
|
+
let state = createInitialState(ast);
|
|
49
|
+
|
|
50
|
+
while (state.status === 'running') {
|
|
51
|
+
state = step(state);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const frame = currentFrame(state);
|
|
55
|
+
expect(frame.locals['visible']).toBeDefined();
|
|
56
|
+
expect(frame.locals['visible'].isPrivate).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('context filtering', () => {
|
|
61
|
+
it('filters private variables from local context', () => {
|
|
62
|
+
const code = `
|
|
63
|
+
let private secret: text = "hidden"
|
|
64
|
+
let visible: text = "shown"
|
|
65
|
+
`;
|
|
66
|
+
const ast = parse(code);
|
|
67
|
+
let state = createInitialState(ast);
|
|
68
|
+
|
|
69
|
+
while (state.status === 'running') {
|
|
70
|
+
state = step(state);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const context = buildLocalContext(state);
|
|
74
|
+
const varNames = context
|
|
75
|
+
.filter((e) => e.kind === 'variable')
|
|
76
|
+
.map((e) => (e as { name: string }).name);
|
|
77
|
+
|
|
78
|
+
expect(varNames).toContain('visible');
|
|
79
|
+
expect(varNames).not.toContain('secret');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('filters private variables from global context', () => {
|
|
83
|
+
const code = `
|
|
84
|
+
let private secret: text = "hidden"
|
|
85
|
+
let visible: text = "shown"
|
|
86
|
+
const private API_KEY: text = "key"
|
|
87
|
+
const PUBLIC: text = "pub"
|
|
88
|
+
`;
|
|
89
|
+
const ast = parse(code);
|
|
90
|
+
let state = createInitialState(ast);
|
|
91
|
+
|
|
92
|
+
while (state.status === 'running') {
|
|
93
|
+
state = step(state);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const context = buildGlobalContext(state);
|
|
97
|
+
const varNames = context
|
|
98
|
+
.filter((e) => e.kind === 'variable')
|
|
99
|
+
.map((e) => (e as { name: string }).name);
|
|
100
|
+
|
|
101
|
+
expect(varNames).toContain('visible');
|
|
102
|
+
expect(varNames).toContain('PUBLIC');
|
|
103
|
+
expect(varNames).not.toContain('secret');
|
|
104
|
+
expect(varNames).not.toContain('API_KEY');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('private variables still exist in runtime but hidden from context', () => {
|
|
108
|
+
const code = `
|
|
109
|
+
let private secret: text = "hidden"
|
|
110
|
+
let visible: text = "shown"
|
|
111
|
+
`;
|
|
112
|
+
const ast = parse(code);
|
|
113
|
+
let state = createInitialState(ast);
|
|
114
|
+
|
|
115
|
+
while (state.status === 'running') {
|
|
116
|
+
state = step(state);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const frame = currentFrame(state);
|
|
120
|
+
|
|
121
|
+
// Both exist in locals
|
|
122
|
+
expect(frame.locals['secret']).toBeDefined();
|
|
123
|
+
expect(frame.locals['visible']).toBeDefined();
|
|
124
|
+
|
|
125
|
+
// Only visible appears in context
|
|
126
|
+
const context = buildLocalContext(state);
|
|
127
|
+
expect(context.length).toBe(1);
|
|
128
|
+
expect((context[0] as { name: string }).name).toBe('visible');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('filters private fields in destructuring from context', () => {
|
|
132
|
+
// For destructuring, we test that the orderedEntries correctly store isPrivate
|
|
133
|
+
const code = 'let private x: text = "a"';
|
|
134
|
+
const ast = parse(code);
|
|
135
|
+
let state = createInitialState(ast);
|
|
136
|
+
|
|
137
|
+
while (state.status === 'running') {
|
|
138
|
+
state = step(state);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const frame = currentFrame(state);
|
|
142
|
+
const entry = frame.orderedEntries[0];
|
|
143
|
+
expect(entry.kind).toBe('variable');
|
|
144
|
+
if (entry.kind === 'variable') {
|
|
145
|
+
expect(entry.name).toBe('x');
|
|
146
|
+
expect(entry.isPrivate).toBe(true);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Verify filtered from context
|
|
150
|
+
const context = buildLocalContext(state);
|
|
151
|
+
expect(context.length).toBe(0);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('mixed visibility scenarios', () => {
|
|
156
|
+
it('handles multiple private and public vars correctly', () => {
|
|
157
|
+
const code = `
|
|
158
|
+
let private a: text = "1"
|
|
159
|
+
let b: text = "2"
|
|
160
|
+
let private c: text = "3"
|
|
161
|
+
let d: text = "4"
|
|
162
|
+
let private e: text = "5"
|
|
163
|
+
`;
|
|
164
|
+
const ast = parse(code);
|
|
165
|
+
let state = createInitialState(ast);
|
|
166
|
+
|
|
167
|
+
while (state.status === 'running') {
|
|
168
|
+
state = step(state);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const context = buildLocalContext(state);
|
|
172
|
+
const varNames = context
|
|
173
|
+
.filter((e) => e.kind === 'variable')
|
|
174
|
+
.map((e) => (e as { name: string }).name);
|
|
175
|
+
|
|
176
|
+
// Only b and d should be visible
|
|
177
|
+
expect(varNames).toEqual(['b', 'd']);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('destructuring with mixed visibility', () => {
|
|
182
|
+
it('simulates destructuring: private x is hidden, public y is visible', () => {
|
|
183
|
+
// Destructuring requires a do/vibe expression which needs AI.
|
|
184
|
+
// We simulate the result by manually creating variables as if
|
|
185
|
+
// `let {private x: text, y: number} = do "..." model` had executed.
|
|
186
|
+
|
|
187
|
+
const code = 'let placeholder = 0'; // Need some code to initialize state
|
|
188
|
+
const ast = parse(code);
|
|
189
|
+
let state = createInitialState(ast);
|
|
190
|
+
|
|
191
|
+
// Execute to get initial state set up
|
|
192
|
+
while (state.status === 'running') {
|
|
193
|
+
state = step(state);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Now manually add variables as if destructuring had happened
|
|
197
|
+
const frame = currentFrame(state);
|
|
198
|
+
|
|
199
|
+
// Simulate: let {private x: text, y: number} = ...
|
|
200
|
+
// x is private (isPrivate: true), y is public (no isPrivate)
|
|
201
|
+
const newLocals = {
|
|
202
|
+
...frame.locals,
|
|
203
|
+
x: {
|
|
204
|
+
value: 'secret-value',
|
|
205
|
+
err: false,
|
|
206
|
+
errDetails: null,
|
|
207
|
+
toolCalls: [],
|
|
208
|
+
isConst: false,
|
|
209
|
+
typeAnnotation: 'text' as const,
|
|
210
|
+
source: 'ai' as const,
|
|
211
|
+
isPrivate: true, // x is private
|
|
212
|
+
},
|
|
213
|
+
y: {
|
|
214
|
+
value: 42,
|
|
215
|
+
err: false,
|
|
216
|
+
errDetails: null,
|
|
217
|
+
toolCalls: [],
|
|
218
|
+
isConst: false,
|
|
219
|
+
typeAnnotation: 'number' as const,
|
|
220
|
+
source: 'ai' as const,
|
|
221
|
+
// y has no isPrivate (public)
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const newOrderedEntries = [
|
|
226
|
+
...frame.orderedEntries,
|
|
227
|
+
{
|
|
228
|
+
kind: 'variable' as const,
|
|
229
|
+
name: 'x',
|
|
230
|
+
value: 'secret-value',
|
|
231
|
+
type: 'text',
|
|
232
|
+
isConst: false,
|
|
233
|
+
source: 'ai' as const,
|
|
234
|
+
isPrivate: true, // x is private
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
kind: 'variable' as const,
|
|
238
|
+
name: 'y',
|
|
239
|
+
value: 42,
|
|
240
|
+
type: 'number',
|
|
241
|
+
isConst: false,
|
|
242
|
+
source: 'ai' as const,
|
|
243
|
+
// y has no isPrivate (public)
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
state = {
|
|
248
|
+
...state,
|
|
249
|
+
callStack: [
|
|
250
|
+
...state.callStack.slice(0, -1),
|
|
251
|
+
{ ...frame, locals: newLocals, orderedEntries: newOrderedEntries },
|
|
252
|
+
],
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Verify both exist in runtime
|
|
256
|
+
const updatedFrame = currentFrame(state);
|
|
257
|
+
expect(updatedFrame.locals['x']).toBeDefined();
|
|
258
|
+
expect(updatedFrame.locals['x'].value).toBe('secret-value');
|
|
259
|
+
expect(updatedFrame.locals['x'].isPrivate).toBe(true);
|
|
260
|
+
|
|
261
|
+
expect(updatedFrame.locals['y']).toBeDefined();
|
|
262
|
+
expect(updatedFrame.locals['y'].value).toBe(42);
|
|
263
|
+
expect(updatedFrame.locals['y'].isPrivate).toBeUndefined();
|
|
264
|
+
|
|
265
|
+
// Verify LOCAL context filtering: x hidden, y visible
|
|
266
|
+
const localContext = buildLocalContext(state);
|
|
267
|
+
const localVarNames = localContext
|
|
268
|
+
.filter((e) => e.kind === 'variable')
|
|
269
|
+
.map((e) => (e as { name: string }).name);
|
|
270
|
+
|
|
271
|
+
expect(localVarNames).toContain('y');
|
|
272
|
+
expect(localVarNames).not.toContain('x');
|
|
273
|
+
expect(localVarNames).toContain('placeholder'); // public
|
|
274
|
+
|
|
275
|
+
// Verify GLOBAL context filtering: x hidden, y visible
|
|
276
|
+
const globalContext = buildGlobalContext(state);
|
|
277
|
+
const globalVarNames = globalContext
|
|
278
|
+
.filter((e) => e.kind === 'variable')
|
|
279
|
+
.map((e) => (e as { name: string }).name);
|
|
280
|
+
|
|
281
|
+
expect(globalVarNames).toContain('y');
|
|
282
|
+
expect(globalVarNames).not.toContain('x');
|
|
283
|
+
expect(globalVarNames).toContain('placeholder'); // public
|
|
284
|
+
|
|
285
|
+
// Verify FORMATTED TEXT doesn't contain private variable
|
|
286
|
+
// This is the actual text sent to AI
|
|
287
|
+
const formattedLocal = formatContextForAI(localContext);
|
|
288
|
+
expect(formattedLocal.text).toContain('y');
|
|
289
|
+
expect(formattedLocal.text).not.toContain('secret-value'); // x's value
|
|
290
|
+
expect(formattedLocal.text).not.toMatch(/\bx\b.*secret/); // x: secret pattern
|
|
291
|
+
|
|
292
|
+
const formattedGlobal = formatContextForAI(globalContext);
|
|
293
|
+
expect(formattedGlobal.text).toContain('y');
|
|
294
|
+
expect(formattedGlobal.text).not.toContain('secret-value');
|
|
295
|
+
expect(formattedGlobal.text).not.toMatch(/\bx\b.*secret/);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('formatted context text', () => {
|
|
300
|
+
it('private variables do not appear in AI context text', () => {
|
|
301
|
+
const code = `
|
|
302
|
+
let private secret: text = "super-secret-password"
|
|
303
|
+
let visible: text = "public-data"
|
|
304
|
+
`;
|
|
305
|
+
const ast = parse(code);
|
|
306
|
+
let state = createInitialState(ast);
|
|
307
|
+
|
|
308
|
+
while (state.status === 'running') {
|
|
309
|
+
state = step(state);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const localContext = buildLocalContext(state);
|
|
313
|
+
const globalContext = buildGlobalContext(state);
|
|
314
|
+
|
|
315
|
+
// Format context as it would be sent to AI
|
|
316
|
+
const formattedLocal = formatContextForAI(localContext);
|
|
317
|
+
const formattedGlobal = formatContextForAI(globalContext);
|
|
318
|
+
|
|
319
|
+
// Visible variable should appear in text
|
|
320
|
+
expect(formattedLocal.text).toContain('visible');
|
|
321
|
+
expect(formattedLocal.text).toContain('public-data');
|
|
322
|
+
expect(formattedGlobal.text).toContain('visible');
|
|
323
|
+
expect(formattedGlobal.text).toContain('public-data');
|
|
324
|
+
|
|
325
|
+
// Private variable should NOT appear in text
|
|
326
|
+
expect(formattedLocal.text).not.toContain('secret');
|
|
327
|
+
expect(formattedLocal.text).not.toContain('super-secret-password');
|
|
328
|
+
expect(formattedGlobal.text).not.toContain('secret');
|
|
329
|
+
expect(formattedGlobal.text).not.toContain('super-secret-password');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|