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.
Files changed (90) hide show
  1. package/.github/workflows/ci.yml +66 -0
  2. package/README.md +1547 -0
  3. package/create-kimchi-app/README.md +44 -0
  4. package/create-kimchi-app/index.js +214 -0
  5. package/create-kimchi-app/package.json +22 -0
  6. package/editors/README.md +121 -0
  7. package/editors/sublime/KimchiLang.sublime-syntax +138 -0
  8. package/editors/vscode/README.md +90 -0
  9. package/editors/vscode/kimchilang-1.1.0.vsix +0 -0
  10. package/editors/vscode/language-configuration.json +37 -0
  11. package/editors/vscode/package.json +55 -0
  12. package/editors/vscode/src/extension.js +354 -0
  13. package/editors/vscode/syntaxes/kimchi.tmLanguage.json +215 -0
  14. package/examples/api/client.km +36 -0
  15. package/examples/async_pipe.km +58 -0
  16. package/examples/basic.kimchi +109 -0
  17. package/examples/cli_framework/README.md +92 -0
  18. package/examples/cli_framework/calculator.km +61 -0
  19. package/examples/cli_framework/deploy.km +126 -0
  20. package/examples/cli_framework/greeter.km +26 -0
  21. package/examples/config.static +27 -0
  22. package/examples/config.static.js +10 -0
  23. package/examples/env_test.km +37 -0
  24. package/examples/fibonacci.kimchi +17 -0
  25. package/examples/greeter.km +15 -0
  26. package/examples/hello.js +1 -0
  27. package/examples/hello.kimchi +3 -0
  28. package/examples/js_interop.km +42 -0
  29. package/examples/logger_example.km +34 -0
  30. package/examples/memo_fibonacci.km +17 -0
  31. package/examples/myapp/lib/http.js +14 -0
  32. package/examples/myapp/lib/http.km +16 -0
  33. package/examples/myapp/main.km +16 -0
  34. package/examples/myapp/main_with_mock.km +42 -0
  35. package/examples/myapp/services/api.js +18 -0
  36. package/examples/myapp/services/api.km +18 -0
  37. package/examples/new_features.kimchi +52 -0
  38. package/examples/project_example.static +20 -0
  39. package/examples/readme_examples.km +240 -0
  40. package/examples/reduce_pattern_match.km +85 -0
  41. package/examples/regex_match.km +46 -0
  42. package/examples/sample.js +45 -0
  43. package/examples/sample.km +39 -0
  44. package/examples/secrets.static +35 -0
  45. package/examples/secrets.static.js +30 -0
  46. package/examples/shell-example.mjs +144 -0
  47. package/examples/shell_example.km +19 -0
  48. package/examples/stdlib_test.km +22 -0
  49. package/examples/test_example.km +69 -0
  50. package/examples/testing/README.md +88 -0
  51. package/examples/testing/http_client.km +18 -0
  52. package/examples/testing/math.km +48 -0
  53. package/examples/testing/math.test.km +93 -0
  54. package/examples/testing/user_service.km +29 -0
  55. package/examples/testing/user_service.test.km +72 -0
  56. package/examples/use-config.mjs +141 -0
  57. package/examples/use_config.km +13 -0
  58. package/install.sh +59 -0
  59. package/package.json +29 -0
  60. package/pantry/acorn/index.km +1 -0
  61. package/pantry/is_number/index.km +1 -0
  62. package/pantry/is_odd/index.km +2 -0
  63. package/project.static +6 -0
  64. package/src/cli.js +1245 -0
  65. package/src/generator.js +1241 -0
  66. package/src/index.js +141 -0
  67. package/src/js2km.js +568 -0
  68. package/src/lexer.js +822 -0
  69. package/src/linter.js +810 -0
  70. package/src/package-manager.js +307 -0
  71. package/src/parser.js +1876 -0
  72. package/src/static-parser.js +500 -0
  73. package/src/typechecker.js +950 -0
  74. package/stdlib/array.km +0 -0
  75. package/stdlib/bitwise.km +38 -0
  76. package/stdlib/console.km +49 -0
  77. package/stdlib/date.km +97 -0
  78. package/stdlib/function.km +44 -0
  79. package/stdlib/http.km +197 -0
  80. package/stdlib/http.md +333 -0
  81. package/stdlib/index.km +26 -0
  82. package/stdlib/json.km +17 -0
  83. package/stdlib/logger.js +114 -0
  84. package/stdlib/logger.km +104 -0
  85. package/stdlib/math.km +120 -0
  86. package/stdlib/object.km +41 -0
  87. package/stdlib/promise.km +33 -0
  88. package/stdlib/string.km +93 -0
  89. package/stdlib/testing.md +265 -0
  90. 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
+ }