kimchilang 1.0.1
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/.github/workflows/ci.yml +66 -0
- package/README.md +1547 -0
- package/create-kimchi-app/README.md +44 -0
- package/create-kimchi-app/index.js +214 -0
- package/create-kimchi-app/package.json +22 -0
- package/editors/README.md +121 -0
- package/editors/sublime/KimchiLang.sublime-syntax +138 -0
- package/editors/vscode/README.md +90 -0
- package/editors/vscode/kimchilang-1.1.0.vsix +0 -0
- package/editors/vscode/language-configuration.json +37 -0
- package/editors/vscode/package.json +55 -0
- package/editors/vscode/src/extension.js +354 -0
- package/editors/vscode/syntaxes/kimchi.tmLanguage.json +215 -0
- package/examples/api/client.km +36 -0
- package/examples/async_pipe.km +58 -0
- package/examples/basic.kimchi +109 -0
- package/examples/cli_framework/README.md +92 -0
- package/examples/cli_framework/calculator.km +61 -0
- package/examples/cli_framework/deploy.km +126 -0
- package/examples/cli_framework/greeter.km +26 -0
- package/examples/config.static +27 -0
- package/examples/config.static.js +10 -0
- package/examples/env_test.km +37 -0
- package/examples/fibonacci.kimchi +17 -0
- package/examples/greeter.km +15 -0
- package/examples/hello.js +1 -0
- package/examples/hello.kimchi +3 -0
- package/examples/js_interop.km +42 -0
- package/examples/logger_example.km +34 -0
- package/examples/memo_fibonacci.km +17 -0
- package/examples/myapp/lib/http.js +14 -0
- package/examples/myapp/lib/http.km +16 -0
- package/examples/myapp/main.km +16 -0
- package/examples/myapp/main_with_mock.km +42 -0
- package/examples/myapp/services/api.js +18 -0
- package/examples/myapp/services/api.km +18 -0
- package/examples/new_features.kimchi +52 -0
- package/examples/project_example.static +20 -0
- package/examples/readme_examples.km +240 -0
- package/examples/reduce_pattern_match.km +85 -0
- package/examples/regex_match.km +46 -0
- package/examples/sample.js +45 -0
- package/examples/sample.km +39 -0
- package/examples/secrets.static +35 -0
- package/examples/secrets.static.js +30 -0
- package/examples/shell-example.mjs +144 -0
- package/examples/shell_example.km +19 -0
- package/examples/stdlib_test.km +22 -0
- package/examples/test_example.km +69 -0
- package/examples/testing/README.md +88 -0
- package/examples/testing/http_client.km +18 -0
- package/examples/testing/math.km +48 -0
- package/examples/testing/math.test.km +93 -0
- package/examples/testing/user_service.km +29 -0
- package/examples/testing/user_service.test.km +72 -0
- package/examples/use-config.mjs +141 -0
- package/examples/use_config.km +13 -0
- package/install.sh +59 -0
- package/package.json +29 -0
- package/pantry/acorn/index.km +1 -0
- package/pantry/is_number/index.km +1 -0
- package/pantry/is_odd/index.km +2 -0
- package/project.static +6 -0
- package/src/cli.js +1245 -0
- package/src/generator.js +1241 -0
- package/src/index.js +141 -0
- package/src/js2km.js +568 -0
- package/src/lexer.js +822 -0
- package/src/linter.js +810 -0
- package/src/package-manager.js +307 -0
- package/src/parser.js +1876 -0
- package/src/static-parser.js +500 -0
- package/src/typechecker.js +950 -0
- package/stdlib/array.km +0 -0
- package/stdlib/bitwise.km +38 -0
- package/stdlib/console.km +49 -0
- package/stdlib/date.km +97 -0
- package/stdlib/function.km +44 -0
- package/stdlib/http.km +197 -0
- package/stdlib/http.md +333 -0
- package/stdlib/index.km +26 -0
- package/stdlib/json.km +17 -0
- package/stdlib/logger.js +114 -0
- package/stdlib/logger.km +104 -0
- package/stdlib/math.km +120 -0
- package/stdlib/object.km +41 -0
- package/stdlib/promise.km +33 -0
- package/stdlib/string.km +93 -0
- package/stdlib/testing.md +265 -0
- package/test/test.js +599 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Generated from .static file
|
|
2
|
+
|
|
3
|
+
// Secret wrapper class
|
|
4
|
+
class _Secret {
|
|
5
|
+
constructor(value) { this._value = value; }
|
|
6
|
+
toString() { return "********"; }
|
|
7
|
+
valueOf() { return this._value; }
|
|
8
|
+
get value() { return this._value; }
|
|
9
|
+
[Symbol.toPrimitive](hint) { return hint === "string" ? "********" : this._value; }
|
|
10
|
+
}
|
|
11
|
+
function _secret(value) { return new _Secret(value); }
|
|
12
|
+
|
|
13
|
+
export const AppName = "MySecureApp";
|
|
14
|
+
|
|
15
|
+
export const Version = "1.0.0";
|
|
16
|
+
|
|
17
|
+
export const MaxConnections = 100;
|
|
18
|
+
|
|
19
|
+
export const Timeout = 5000;
|
|
20
|
+
|
|
21
|
+
export const DebugMode = false;
|
|
22
|
+
|
|
23
|
+
export const ApiKey = _secret("sk-1234567890abcdef");
|
|
24
|
+
|
|
25
|
+
export const InternalPort = _secret(8443);
|
|
26
|
+
|
|
27
|
+
export const DatabaseConfig = { host: "localhost", port: 5432, database: "myapp", username: _secret("admin"), password: _secret("super-secret-password") };
|
|
28
|
+
|
|
29
|
+
export const ServiceConfig = { name: "api-service", url: "https://api.example.com", token: _secret("bearer-token-12345"), retries: 3 };
|
|
30
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// KimchiLang stdlib extensions
|
|
2
|
+
if (!Array.prototype._kmExtended) {
|
|
3
|
+
Array.prototype._kmExtended = true;
|
|
4
|
+
Array.prototype.first = function() { return this[0]; };
|
|
5
|
+
Array.prototype.last = function() { return this[this.length - 1]; };
|
|
6
|
+
Array.prototype.isEmpty = function() { return this.length === 0; };
|
|
7
|
+
Array.prototype.sum = function() { return this.reduce((a, b) => a + b, 0); };
|
|
8
|
+
Array.prototype.product = function() { return this.reduce((a, b) => a * b, 1); };
|
|
9
|
+
Array.prototype.average = function() { return this.reduce((a, b) => a + b, 0) / this.length; };
|
|
10
|
+
Array.prototype.max = function() { return Math.max(...this); };
|
|
11
|
+
Array.prototype.min = function() { return Math.min(...this); };
|
|
12
|
+
Array.prototype.take = function(n) { return this.slice(0, n); };
|
|
13
|
+
Array.prototype.drop = function(n) { return this.slice(n); };
|
|
14
|
+
Array.prototype.flatten = function() { return this.flat(Infinity); };
|
|
15
|
+
Array.prototype.unique = function() { return [...new Set(this)]; };
|
|
16
|
+
}
|
|
17
|
+
if (!String.prototype._kmExtended) {
|
|
18
|
+
String.prototype._kmExtended = true;
|
|
19
|
+
String.prototype.isEmpty = function() { return this.length === 0; };
|
|
20
|
+
String.prototype.isBlank = function() { return this.trim().length === 0; };
|
|
21
|
+
String.prototype.toChars = function() { return this.split(""); };
|
|
22
|
+
String.prototype.toLines = function() { return this.split("\n"); };
|
|
23
|
+
String.prototype.capitalize = function() { return this.length === 0 ? this : this[0].toUpperCase() + this.slice(1); };
|
|
24
|
+
}
|
|
25
|
+
const _obj = {
|
|
26
|
+
keys: (o) => Object.keys(o),
|
|
27
|
+
values: (o) => Object.values(o),
|
|
28
|
+
entries: (o) => Object.entries(o),
|
|
29
|
+
fromEntries: (arr) => Object.fromEntries(arr),
|
|
30
|
+
has: (o, k) => Object.hasOwn(o, k),
|
|
31
|
+
freeze: (o) => Object.freeze(o),
|
|
32
|
+
isEmpty: (o) => Object.keys(o).length === 0,
|
|
33
|
+
size: (o) => Object.keys(o).length,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function error(message, name = "Error") {
|
|
37
|
+
const e = new Error(message);
|
|
38
|
+
e.name = name;
|
|
39
|
+
return e;
|
|
40
|
+
}
|
|
41
|
+
error.create = (name) => {
|
|
42
|
+
const fn = (message) => error(message, name);
|
|
43
|
+
Object.defineProperty(fn, "name", { value: name, writable: false });
|
|
44
|
+
return fn;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
class _Secret {
|
|
48
|
+
constructor(value) { this._value = value; }
|
|
49
|
+
toString() { return "********"; }
|
|
50
|
+
valueOf() { return this._value; }
|
|
51
|
+
get value() { return this._value; }
|
|
52
|
+
[Symbol.toPrimitive](hint) { return hint === "string" ? "********" : this._value; }
|
|
53
|
+
}
|
|
54
|
+
function _secret(value) { return new _Secret(value); }
|
|
55
|
+
|
|
56
|
+
function _deepFreeze(obj) {
|
|
57
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
58
|
+
Object.keys(obj).forEach(key => _deepFreeze(obj[key]));
|
|
59
|
+
return Object.freeze(obj);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function _shell(command, inputs = {}) {
|
|
63
|
+
const { exec } = await import("child_process");
|
|
64
|
+
const { promisify } = await import("util");
|
|
65
|
+
const execAsync = promisify(exec);
|
|
66
|
+
// Interpolate inputs into command
|
|
67
|
+
let cmd = command;
|
|
68
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
69
|
+
cmd = cmd.replace(new RegExp("\\$" + key + "\\b", "g"), String(value));
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const { stdout, stderr } = await execAsync(cmd);
|
|
73
|
+
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 };
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return { stdout: error.stdout?.trim() || "", stderr: error.stderr?.trim() || error.message, exitCode: error.code || 1 };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Testing framework
|
|
80
|
+
const _tests = [];
|
|
81
|
+
let _currentDescribe = null;
|
|
82
|
+
function _describe(name, fn) {
|
|
83
|
+
const prev = _currentDescribe;
|
|
84
|
+
_currentDescribe = { name, tests: [], parent: prev };
|
|
85
|
+
fn();
|
|
86
|
+
if (prev) { prev.tests.push(_currentDescribe); }
|
|
87
|
+
else { _tests.push(_currentDescribe); }
|
|
88
|
+
_currentDescribe = prev;
|
|
89
|
+
}
|
|
90
|
+
function _test(name, fn) {
|
|
91
|
+
const test = { name, fn, describe: _currentDescribe };
|
|
92
|
+
if (_currentDescribe) { _currentDescribe.tests.push(test); }
|
|
93
|
+
else { _tests.push(test); }
|
|
94
|
+
}
|
|
95
|
+
function _expect(actual) {
|
|
96
|
+
return {
|
|
97
|
+
toBe(expected) { if (actual !== expected) throw new Error(`Expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`); },
|
|
98
|
+
toEqual(expected) { if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error(`Expected ${JSON.stringify(expected)} to equal ${JSON.stringify(actual)}`); },
|
|
99
|
+
toContain(item) { if (!actual.includes(item)) throw new Error(`Expected ${JSON.stringify(actual)} to contain ${JSON.stringify(item)}`); },
|
|
100
|
+
toBeNull() { if (actual !== null) throw new Error(`Expected null but got ${JSON.stringify(actual)}`); },
|
|
101
|
+
toBeTruthy() { if (!actual) throw new Error(`Expected truthy but got ${JSON.stringify(actual)}`); },
|
|
102
|
+
toBeFalsy() { if (actual) throw new Error(`Expected falsy but got ${JSON.stringify(actual)}`); },
|
|
103
|
+
toBeGreaterThan(n) { if (actual <= n) throw new Error(`Expected ${actual} > ${n}`); },
|
|
104
|
+
toBeLessThan(n) { if (actual >= n) throw new Error(`Expected ${actual} < ${n}`); },
|
|
105
|
+
toHaveLength(n) { if (actual.length !== n) throw new Error(`Expected length ${n} but got ${actual.length}`); },
|
|
106
|
+
toMatch(pattern) { if (!pattern.test(actual)) throw new Error(`Expected ${JSON.stringify(actual)} to match ${pattern}`); },
|
|
107
|
+
toThrow(msg) { try { actual(); throw new Error("Expected to throw"); } catch(e) { if (msg && !e.message.includes(msg)) throw new Error(`Expected error containing "${msg}" but got "${e.message}"`); } },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function _assert(condition, message) { if (!condition) throw new Error(message); }
|
|
111
|
+
async function _runTests() {
|
|
112
|
+
let passed = 0, failed = 0;
|
|
113
|
+
async function runItem(item, indent = "") {
|
|
114
|
+
if (item.fn) {
|
|
115
|
+
try { await item.fn(); console.log(indent + "✓ " + item.name); passed++; }
|
|
116
|
+
catch (e) { console.log(indent + "✗ " + item.name); console.log(indent + " " + e.message); failed++; }
|
|
117
|
+
} else {
|
|
118
|
+
console.log(indent + item.name);
|
|
119
|
+
for (const t of item.tests) await runItem(t, indent + " ");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const item of _tests) await runItem(item);
|
|
123
|
+
console.log(`\n${passed + failed} tests, ${passed} passed, ${failed} failed`);
|
|
124
|
+
return { passed, failed };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default function(_opts = {}) {
|
|
128
|
+
async function listFiles() {
|
|
129
|
+
const result = _deepFreeze(await _shell("ls -la"));
|
|
130
|
+
console.log(result?.stdout);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function getDate() {
|
|
134
|
+
const result = _deepFreeze(await _shell("date"));
|
|
135
|
+
return result?.stdout;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function findFiles(pattern) {
|
|
139
|
+
const result = _deepFreeze(await _shell("find . -name \"$pattern\"", { pattern }));
|
|
140
|
+
return result?.stdout;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
listFiles();
|
|
144
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Example of shell interop in KimchiLang
|
|
2
|
+
|
|
3
|
+
fn listFiles() {
|
|
4
|
+
dec result = shell { ls -la }
|
|
5
|
+
print result.stdout
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
fn getDate() {
|
|
9
|
+
dec result = shell { date }
|
|
10
|
+
return result.stdout
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Shell with inputs
|
|
14
|
+
fn findFiles(pattern) {
|
|
15
|
+
dec result = shell(pattern) { find . -name "$pattern" }
|
|
16
|
+
return result.stdout
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
listFiles()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Test stdlib extensions on literals
|
|
2
|
+
|
|
3
|
+
expose fn _describe() {
|
|
4
|
+
return "Test that literals have stdlib methods"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Array extensions
|
|
8
|
+
dec nums = [1, 2, 3, 4, 5]
|
|
9
|
+
print "Array tests:"
|
|
10
|
+
print "first: ${nums.first()}"
|
|
11
|
+
print "last: ${nums.last()}"
|
|
12
|
+
print "sum: ${nums.sum()}"
|
|
13
|
+
print "average: ${nums.average()}"
|
|
14
|
+
print "max: ${nums.max()}"
|
|
15
|
+
print "min: ${nums.min()}"
|
|
16
|
+
|
|
17
|
+
// String extensions
|
|
18
|
+
dec text = "hello world"
|
|
19
|
+
print "\nString tests:"
|
|
20
|
+
print "capitalize: ${text.capitalize()}"
|
|
21
|
+
print "isEmpty: ${text.isEmpty()}"
|
|
22
|
+
print "isBlank: ${" ".isBlank()}"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Example test file demonstrating KimchiLang's built-in testing framework
|
|
2
|
+
|
|
3
|
+
// Helper function to test
|
|
4
|
+
fn add(a, b) {
|
|
5
|
+
return a + b
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
fn multiply(a, b) {
|
|
9
|
+
return a * b
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fn greet(name) {
|
|
13
|
+
return "Hello, ${name}!"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Simple tests
|
|
17
|
+
test "addition works" {
|
|
18
|
+
expect(add(2, 3)).toBe(5)
|
|
19
|
+
expect(add(-1, 1)).toBe(0)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test "multiplication works" {
|
|
23
|
+
expect(multiply(3, 4)).toBe(12)
|
|
24
|
+
expect(multiply(0, 100)).toBe(0)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Grouped tests with describe
|
|
28
|
+
describe "String functions" {
|
|
29
|
+
test "greet returns correct message" {
|
|
30
|
+
expect(greet("World")).toBe("Hello, World!")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test "string contains check" {
|
|
34
|
+
dec message = greet("Alice")
|
|
35
|
+
expect(message).toContain("Alice")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe "Array operations" {
|
|
40
|
+
test "array length" {
|
|
41
|
+
dec arr = [1, 2, 3, 4, 5]
|
|
42
|
+
expect(arr).toHaveLength(5)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
test "array contains" {
|
|
46
|
+
dec fruits = ["apple", "banana", "cherry"]
|
|
47
|
+
expect(fruits).toContain("banana")
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Using assert
|
|
52
|
+
test "assert examples" {
|
|
53
|
+
assert 1 + 1 == 2, "Basic math should work"
|
|
54
|
+
assert true, "True should be truthy"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Testing comparisons
|
|
58
|
+
test "comparison matchers" {
|
|
59
|
+
expect(10).toBeGreaterThan(5)
|
|
60
|
+
expect(3).toBeLessThan(7)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Testing truthiness
|
|
64
|
+
test "truthy and falsy" {
|
|
65
|
+
expect(true).toBeTruthy()
|
|
66
|
+
expect(false).toBeFalsy()
|
|
67
|
+
expect(null).toBeFalsy()
|
|
68
|
+
expect("hello").toBeTruthy()
|
|
69
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# KimchiLang Testing Examples
|
|
2
|
+
|
|
3
|
+
This directory demonstrates how to write unit tests in KimchiLang.
|
|
4
|
+
|
|
5
|
+
## Running Tests
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run math tests
|
|
9
|
+
kimchi examples.testing.math.test
|
|
10
|
+
|
|
11
|
+
# Run user service tests (with mocked dependencies)
|
|
12
|
+
kimchi examples.testing.user_service.test
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Test Syntax
|
|
16
|
+
|
|
17
|
+
### Basic Test
|
|
18
|
+
|
|
19
|
+
```kimchi
|
|
20
|
+
test "description of test" {
|
|
21
|
+
expect(actual).toBe(expected)
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Grouped Tests with Describe
|
|
26
|
+
|
|
27
|
+
```kimchi
|
|
28
|
+
describe "Feature Name" {
|
|
29
|
+
test "specific behavior" {
|
|
30
|
+
expect(result).toBe(expected)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test "another behavior" {
|
|
34
|
+
expect(other).toEqual(expected)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Available Matchers
|
|
40
|
+
|
|
41
|
+
| Matcher | Description |
|
|
42
|
+
|---------|-------------|
|
|
43
|
+
| `toBe(value)` | Strict equality (`===`) |
|
|
44
|
+
| `toEqual(value)` | Deep equality (JSON comparison) |
|
|
45
|
+
| `toContain(item)` | Array/string contains item |
|
|
46
|
+
| `toBeNull()` | Value is `null` |
|
|
47
|
+
| `toBeTruthy()` | Value is truthy |
|
|
48
|
+
| `toBeFalsy()` | Value is falsy |
|
|
49
|
+
| `toBeGreaterThan(n)` | Value > n |
|
|
50
|
+
| `toBeLessThan(n)` | Value < n |
|
|
51
|
+
| `toHaveLength(n)` | Array/string length equals n |
|
|
52
|
+
| `toMatch(regex)` | String matches regex |
|
|
53
|
+
| `toThrow(message)` | Function throws error containing message |
|
|
54
|
+
|
|
55
|
+
### Assert Statement
|
|
56
|
+
|
|
57
|
+
```kimchi
|
|
58
|
+
assert condition, "Error message if false"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Testing with Mocks
|
|
62
|
+
|
|
63
|
+
KimchiLang's dependency injection makes mocking easy:
|
|
64
|
+
|
|
65
|
+
```kimchi
|
|
66
|
+
// Create a mock
|
|
67
|
+
dec mockDep = {
|
|
68
|
+
someMethod: (arg) => "mocked result"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Inject mock when importing
|
|
72
|
+
as myModule dep path.to.module({
|
|
73
|
+
"path.to.dependency": mockDep
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Now myModule uses the mock instead of real dependency
|
|
77
|
+
test "uses mock" {
|
|
78
|
+
expect(myModule.doSomething()).toBe("mocked result")
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Files in This Directory
|
|
83
|
+
|
|
84
|
+
- `math.km` - Math utility functions
|
|
85
|
+
- `math.test.km` - Tests for math module
|
|
86
|
+
- `http_client.km` - Simple HTTP client stub
|
|
87
|
+
- `user_service.km` - User service that depends on HTTP client
|
|
88
|
+
- `user_service.test.km` - Tests with mocked HTTP client
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// HTTP client module - simple stub for demonstration
|
|
2
|
+
// In real usage, this would make actual HTTP requests
|
|
3
|
+
|
|
4
|
+
expose fn get(url) {
|
|
5
|
+
return { status: 200, data: null }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
expose fn post(url, body) {
|
|
9
|
+
return { status: 201, data: body }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
expose fn put(url, body) {
|
|
13
|
+
return { status: 200, data: body }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
expose fn delete(url) {
|
|
17
|
+
return { status: 200 }
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Math utility module
|
|
2
|
+
// Example module to demonstrate unit testing
|
|
3
|
+
|
|
4
|
+
expose fn add(a, b) {
|
|
5
|
+
return a + b
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
expose fn subtract(a, b) {
|
|
9
|
+
return a - b
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
expose fn multiply(a, b) {
|
|
13
|
+
return a * b
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
expose fn divide(a, b) {
|
|
17
|
+
if b == 0 {
|
|
18
|
+
throw js { return new Error("Cannot divide by zero"); }
|
|
19
|
+
}
|
|
20
|
+
return a / b
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
expose fn factorial(n) {
|
|
24
|
+
|n < 0| => throw js { return new Error("Factorial not defined for negative numbers"); }
|
|
25
|
+
|n <= 1| => return 1
|
|
26
|
+
|true| => return n * factorial(n - 1)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
expose fn fibonacci(n) {
|
|
30
|
+
|n <= 0| => return 0
|
|
31
|
+
|n == 1| => return 1
|
|
32
|
+
|true| => return fibonacci(n - 1) + fibonacci(n - 2)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
expose fn isPrime(n) {
|
|
36
|
+
|n <= 1| => return false
|
|
37
|
+
|n <= 3| => return true
|
|
38
|
+
|n % 2 == 0| => return false
|
|
39
|
+
|
|
40
|
+
dec i = 3
|
|
41
|
+
// Use JS for the loop since we need mutation
|
|
42
|
+
return js(n, i) {
|
|
43
|
+
for (let i = 3; i * i <= n; i += 2) {
|
|
44
|
+
if (n % i === 0) return false;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Unit tests for math.km module
|
|
2
|
+
// Run with: kimchi examples.testing.math.test
|
|
3
|
+
|
|
4
|
+
as math dep examples.testing.math
|
|
5
|
+
|
|
6
|
+
// Test basic arithmetic operations
|
|
7
|
+
describe "Basic Arithmetic" {
|
|
8
|
+
test "add returns correct sum" {
|
|
9
|
+
expect(math.add(2, 3)).toBe(5)
|
|
10
|
+
expect(math.add(-1, 1)).toBe(0)
|
|
11
|
+
expect(math.add(0, 0)).toBe(0)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test "subtract returns correct difference" {
|
|
15
|
+
expect(math.subtract(5, 3)).toBe(2)
|
|
16
|
+
expect(math.subtract(3, 5)).toBe(-2)
|
|
17
|
+
expect(math.subtract(0, 0)).toBe(0)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test "multiply returns correct product" {
|
|
21
|
+
expect(math.multiply(3, 4)).toBe(12)
|
|
22
|
+
expect(math.multiply(-2, 3)).toBe(-6)
|
|
23
|
+
expect(math.multiply(0, 100)).toBe(0)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test "divide returns correct quotient" {
|
|
27
|
+
expect(math.divide(10, 2)).toBe(5)
|
|
28
|
+
expect(math.divide(7, 2)).toBe(3.5)
|
|
29
|
+
expect(math.divide(-6, 2)).toBe(-3)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test "divide by zero throws error" {
|
|
33
|
+
expect(() => math.divide(10, 0)).toThrow("Cannot divide by zero")
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Test factorial function
|
|
38
|
+
describe "Factorial" {
|
|
39
|
+
test "factorial of 0 is 1" {
|
|
40
|
+
expect(math.factorial(0)).toBe(1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test "factorial of 1 is 1" {
|
|
44
|
+
expect(math.factorial(1)).toBe(1)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test "factorial of positive numbers" {
|
|
48
|
+
expect(math.factorial(5)).toBe(120)
|
|
49
|
+
expect(math.factorial(6)).toBe(720)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test "factorial of negative throws error" {
|
|
53
|
+
expect(() => math.factorial(-1)).toThrow("negative")
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Test fibonacci function
|
|
58
|
+
describe "Fibonacci" {
|
|
59
|
+
test "fibonacci base cases" {
|
|
60
|
+
expect(math.fibonacci(0)).toBe(0)
|
|
61
|
+
expect(math.fibonacci(1)).toBe(1)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
test "fibonacci sequence" {
|
|
65
|
+
expect(math.fibonacci(2)).toBe(1)
|
|
66
|
+
expect(math.fibonacci(3)).toBe(2)
|
|
67
|
+
expect(math.fibonacci(4)).toBe(3)
|
|
68
|
+
expect(math.fibonacci(5)).toBe(5)
|
|
69
|
+
expect(math.fibonacci(10)).toBe(55)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Test prime checking
|
|
74
|
+
describe "Prime Numbers" {
|
|
75
|
+
test "small primes are identified" {
|
|
76
|
+
expect(math.isPrime(2)).toBe(true)
|
|
77
|
+
expect(math.isPrime(3)).toBe(true)
|
|
78
|
+
expect(math.isPrime(5)).toBe(true)
|
|
79
|
+
expect(math.isPrime(7)).toBe(true)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
test "non-primes are identified" {
|
|
83
|
+
expect(math.isPrime(0)).toBe(false)
|
|
84
|
+
expect(math.isPrime(1)).toBe(false)
|
|
85
|
+
expect(math.isPrime(4)).toBe(false)
|
|
86
|
+
expect(math.isPrime(9)).toBe(false)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
test "larger primes" {
|
|
90
|
+
expect(math.isPrime(97)).toBe(true)
|
|
91
|
+
expect(math.isPrime(100)).toBe(false)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// User service module - demonstrates testing with dependency injection
|
|
2
|
+
// This module depends on an HTTP client that can be mocked in tests
|
|
3
|
+
|
|
4
|
+
as http dep examples.testing.http_client
|
|
5
|
+
|
|
6
|
+
expose fn getUser(id) {
|
|
7
|
+
dec response = http.get("/users/${id}")
|
|
8
|
+
return response.data
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
expose fn createUser(name, email) {
|
|
12
|
+
dec response = http.post("/users", { name: name, email: email })
|
|
13
|
+
return response.data
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
expose fn updateUser(id, updates) {
|
|
17
|
+
dec response = http.put("/users/${id}", updates)
|
|
18
|
+
return response.data
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
expose fn deleteUser(id) {
|
|
22
|
+
dec response = http.delete("/users/${id}")
|
|
23
|
+
return response.status == 200
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
expose fn getUsersByRole(role) {
|
|
27
|
+
dec response = http.get("/users?role=${role}")
|
|
28
|
+
return response.data
|
|
29
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Unit tests for user-service.km with mocked HTTP client
|
|
2
|
+
// Demonstrates dependency injection for testing
|
|
3
|
+
// Run with: kimchi examples.testing.user-service.test
|
|
4
|
+
|
|
5
|
+
// Create a mock HTTP client
|
|
6
|
+
dec mockHttp = {
|
|
7
|
+
get: (url) => {
|
|
8
|
+
|url == "/users/1"| => return { status: 200, data: { id: 1, name: "Alice", email: "alice@test.com" } }
|
|
9
|
+
|url == "/users?role=admin"| => return { status: 200, data: [{ id: 1, name: "Admin" }] }
|
|
10
|
+
|true| => return { status: 404, data: null }
|
|
11
|
+
},
|
|
12
|
+
post: (url, body) => {
|
|
13
|
+
return { status: 201, data: { id: 99, ...body } }
|
|
14
|
+
},
|
|
15
|
+
put: (url, body) => {
|
|
16
|
+
return { status: 200, data: { id: 1, ...body } }
|
|
17
|
+
},
|
|
18
|
+
delete: (url) => {
|
|
19
|
+
return { status: 200 }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Import user service with mocked HTTP client
|
|
24
|
+
as userService dep examples.testing.user_service({
|
|
25
|
+
"examples.testing.http_client": mockHttp
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe "User Service" {
|
|
29
|
+
describe "getUser" {
|
|
30
|
+
test "returns user data for valid id" {
|
|
31
|
+
dec user = userService.getUser(1)
|
|
32
|
+
expect(user.name).toBe("Alice")
|
|
33
|
+
expect(user.email).toBe("alice@test.com")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test "returns null for invalid id" {
|
|
37
|
+
dec user = userService.getUser(999)
|
|
38
|
+
expect(user).toBeNull()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe "createUser" {
|
|
43
|
+
test "creates user with provided data" {
|
|
44
|
+
dec user = userService.createUser("Bob", "bob@test.com")
|
|
45
|
+
expect(user.name).toBe("Bob")
|
|
46
|
+
expect(user.email).toBe("bob@test.com")
|
|
47
|
+
expect(user.id).toBe(99)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe "updateUser" {
|
|
52
|
+
test "updates user data" {
|
|
53
|
+
dec user = userService.updateUser(1, { name: "Alice Updated" })
|
|
54
|
+
expect(user.name).toBe("Alice Updated")
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe "deleteUser" {
|
|
59
|
+
test "returns true on successful delete" {
|
|
60
|
+
dec result = userService.deleteUser(1)
|
|
61
|
+
expect(result).toBe(true)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe "getUsersByRole" {
|
|
66
|
+
test "returns users filtered by role" {
|
|
67
|
+
dec admins = userService.getUsersByRole("admin")
|
|
68
|
+
expect(admins).toHaveLength(1)
|
|
69
|
+
expect(admins[0].name).toBe("Admin")
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|