From d56dca6f313e81a2c9ecafea86b0f5a6625be4de Mon Sep 17 00:00:00 2001 From: Sergey Malenkov Date: Mon, 20 Jan 2025 18:02:26 +0300 Subject: [PATCH] Harness for KoalaUI Signed-off-by: Sergey Malenkov --- .gitlab-ci.yml | 1 + arkoala-arkts/libarkts/test/test-util.ts | 6 +- arkoala-arkts/package.json | 2 +- arkoala/arkui-common/oh-package.json5 | 3 + .../arkui-common/test/prop-deep-copy.test.ts | 28 +- arkoala/arkui/oh-package.json5 | 3 +- arkoala/arkui/package.json | 5 +- arkoala/ets-plugin/.gitlab-ci.yml | 10 +- arkoala/ets-plugin/oh-package.json5 | 3 +- arkoala/ets-plugin/package.json | 2 +- arkoala/ets-plugin/test/utils.ts | 6 +- arkoala/har/arkoala-har-bundle/package.json | 1 + arkoala/package.json | 3 +- .../common/bridges/ohos/src/chai/index.ts | 213 ----- incremental/common/oh-package.json5 | 19 +- incremental/common/package.json | 7 +- incremental/common/tsconfig.json | 3 +- incremental/compat/oh-package.json5 | 4 - incremental/compat/package.json | 4 - incremental/compiler-plugin/.gitlab-ci.yml | 3 +- incremental/compiler-plugin/oh-package.json5 | 3 +- incremental/compiler-plugin/package.json | 5 +- .../compiler-plugin/test/auto_detect.test.ts | 10 +- .../compiler-plugin/test/basic.test.ts | 28 +- .../test/diagnostics/diagnostics.test.ts | 10 +- .../test/function_return.test.ts | 8 +- .../test/parenthesised-call.test.ts | 3 +- incremental/compiler-plugin/test/util.test.ts | 6 +- .../test/variable_declaration.test.ts | 3 +- .../compiler-plugin/test/whole_file.test.ts | 6 +- .../test/wrap_object_literal.test.ts | 10 +- incremental/compiler-plugin/tsconfig.json | 1 + incremental/harness/.gitlab-ci.yml | 33 + incremental/harness/README.md | 1 + incremental/harness/arktsconfig.json | 12 + .../{common/test => harness/js}/golden.js | 2 +- incremental/harness/oh-package.json5 | 37 + incremental/harness/package.json | 49 ++ .../harness.ts => harness/src/arkts/chai.ts} | 148 +--- .../ohos/src => harness/src/arkts}/index.ts | 8 +- incremental/harness/src/arkts/mocha.ts | 96 +++ incremental/harness/src/index.ts | 16 + incremental/harness/src/ohos/chai.ts | 782 ++++++++++++++++++ .../index.d.ts => harness/src/ohos/index.ts} | 30 +- .../index.ts => harness/src/ohos/mocha.ts} | 45 +- incremental/harness/src/typescript/index.ts | 21 + .../tsconfig-ohos.json} | 14 +- incremental/harness/tsconfig.json | 15 + incremental/package.json | 5 +- incremental/runtime/.gitlab-ci.yml | 3 +- .../runtime/arktsconfig-run-unmemoized.json | 2 + incremental/runtime/arktsconfig-test-app.json | 1 + incremental/runtime/arktsconfig-test-lib.json | 1 + .../runtime/arktsconfig-unmemoize.json | 1 + incremental/runtime/arktsconfig.json | 2 + incremental/runtime/oh-package.json5 | 5 +- incremental/runtime/package.json | 3 +- .../test-arkts/animation/Easing.test.ts | 5 +- .../test-arkts/common/MarkableQueue.test.ts | 107 +++ .../test-arkts/memo/changeListener.test.ts | 5 +- .../test-arkts/memo/contextLocal.test.ts | 5 +- .../runtime/test-arkts/memo/remember.test.ts | 5 +- .../runtime/test-arkts/memo/repeat.test.ts | 38 +- .../runtime/test-arkts/states/Journal.test.ts | 99 +++ .../runtime/test-arkts/states/State.test.ts | 33 +- .../test-arkts/states/state_basics.test.ts | 5 +- .../runtime/test-arkts/tree/TreeNode.test.ts | 13 +- .../runtime/test-arkts/tree/TreePath.test.ts | 5 +- .../runtime/test/animation/Easing.test.ts | 6 +- .../runtime/test/common/MarkableQueue.test.ts | 4 +- .../runtime/test/memo/changeListener.test.ts | 8 +- .../runtime/test/memo/contextLocal.test.ts | 8 +- .../runtime/test/memo/remember.test.ts | 6 +- incremental/runtime/test/memo/repeat.test.ts | 6 +- .../runtime/test/states/Journal.test.ts | 4 +- incremental/runtime/test/states/State.test.ts | 538 ++++++------ .../runtime/test/states/state_basics.test.ts | 74 +- .../runtime/test/tree/TreeNode.test.ts | 136 +-- .../runtime/test/tree/TreePath.test.ts | 24 +- .../runtime/tsconfig-run-unmemoized.json | 1 + incremental/runtime/tsconfig-test.json | 1 + incremental/runtime/tsconfig-unmemoize.json | 1 + incremental/runtime/tsconfig.json | 3 +- incremental/test-utils/scripts/register.js | 4 +- interop/oh-package.json5 | 2 - interop/package.json | 2 +- 86 files changed, 1925 insertions(+), 974 deletions(-) delete mode 100644 incremental/common/bridges/ohos/src/chai/index.ts create mode 100644 incremental/harness/.gitlab-ci.yml create mode 100644 incremental/harness/README.md create mode 100644 incremental/harness/arktsconfig.json rename incremental/{common/test => harness/js}/golden.js (96%) create mode 100644 incremental/harness/oh-package.json5 create mode 100644 incremental/harness/package.json rename incremental/{runtime/test-arkts/harness.ts => harness/src/arkts/chai.ts} (86%) rename incremental/{common/bridges/ohos/src => harness/src/arkts}/index.ts (78%) create mode 100644 incremental/harness/src/arkts/mocha.ts create mode 100644 incremental/harness/src/index.ts create mode 100644 incremental/harness/src/ohos/chai.ts rename incremental/{common/bridges/ohos/src/mocha/types/index.d.ts => harness/src/ohos/index.ts} (57%) rename incremental/{common/bridges/ohos/src/mocha/index.ts => harness/src/ohos/mocha.ts} (71%) create mode 100644 incremental/harness/src/typescript/index.ts rename incremental/{common/bridges/ohos/tsconfig.json => harness/tsconfig-ohos.json} (37%) create mode 100644 incremental/harness/tsconfig.json create mode 100644 incremental/runtime/test-arkts/common/MarkableQueue.test.ts create mode 100644 incremental/runtime/test-arkts/states/Journal.test.ts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index febaee23f..3a21196fe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,6 +23,7 @@ include: - incremental/build-common/.gitlab-ci.yml - incremental/compat/.gitlab-ci.yml - incremental/common/.gitlab-ci.yml + - incremental/harness/.gitlab-ci.yml - incremental/compiler-plugin/.gitlab-ci.yml - incremental/runtime/.gitlab-ci.yml - incremental/demo-playground/.gitlab-ci.yml diff --git a/arkoala-arkts/libarkts/test/test-util.ts b/arkoala-arkts/libarkts/test/test-util.ts index 0898cad4b..5eb4a4b9c 100644 --- a/arkoala-arkts/libarkts/test/test-util.ts +++ b/arkoala-arkts/libarkts/test/test-util.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -18,11 +18,11 @@ import * as ts from "../src/ts-api" import * as arkts from "../src/arkts-api" import * as pth from "path" -import { assert } from "chai" +import { Assert as assert } from "@koalaui/harness" import { exec, execSync } from "child_process" export { Es2pandaNativeModule } from "../src/Es2pandaNativeModule" -export { assert } from "chai" +export { Assert as assert } from "@koalaui/harness" class defaultTransformationContext implements ts.TransformationContext { } diff --git a/arkoala-arkts/package.json b/arkoala-arkts/package.json index cdc4e2c95..88ae02e09 100644 --- a/arkoala-arkts/package.json +++ b/arkoala-arkts/package.json @@ -11,6 +11,7 @@ "../incremental/build-common", "../incremental/compiler-plugin", "../incremental/common", + "../incremental/harness", "../incremental/runtime", "../interop" ], @@ -21,7 +22,6 @@ "@types/chai": "^4.3.1", "@types/mocha": "^9.1.0", "bin-links": "^4.0.4", - "chai": "^4.3.6", "chalk": "^4.1.2", "copy-webpack-plugin": "^10.2.4", "minimist": "1.2.8", diff --git a/arkoala/arkui-common/oh-package.json5 b/arkoala/arkui-common/oh-package.json5 index 58c42e3b6..aee6848f3 100644 --- a/arkoala/arkui-common/oh-package.json5 +++ b/arkoala/arkui-common/oh-package.json5 @@ -43,5 +43,8 @@ "@koalaui/compat": "1.4.1+devel", "@koalaui/runtime": "1.4.1+devel", "@koalaui/ets-tsc": "4.9.5-r4" + }, + "devDependencies": { + "@koalaui/harness": "1.4.1+devel" } } diff --git a/arkoala/arkui-common/test/prop-deep-copy.test.ts b/arkoala/arkui-common/test/prop-deep-copy.test.ts index 2a0f64947..8b2d07d14 100644 --- a/arkoala/arkui-common/test/prop-deep-copy.test.ts +++ b/arkoala/arkui-common/test/prop-deep-copy.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -14,7 +14,7 @@ */ import { propDeepCopy } from "@koalaui/common" -import { assert } from "chai" +import { Assert } from "@koalaui/harness" suite("@Prop deep copy", () => { test("Complex object", () => { @@ -31,26 +31,26 @@ suite("@Prop deep copy", () => { const copy = propDeepCopy(object) - assert.equal(object.dynamicProperty.nested, copy.dynamicProperty.nested) - assert.equal(object.has(initialNumber), copy.has(initialNumber)) - assert.equal(object.foo(), copy.foo()) + Assert.equal(object.dynamicProperty.nested, copy.dynamicProperty.nested) + Assert.equal(object.has(initialNumber), copy.has(initialNumber)) + Assert.equal(object.foo(), copy.foo()) copy.delete(initialNumber) copy.dynamicProperty.nested = changedNumber copy.foo = () => { return changedNumber } - assert.notEqual(object.dynamicProperty.nested, copy.dynamicProperty.nested) - assert.notEqual(object.has(initialNumber), copy.has(initialNumber)) - assert.notEqual(object.foo(), copy.foo()) + Assert.notEqual(object.dynamicProperty.nested, copy.dynamicProperty.nested) + Assert.notEqual(object.has(initialNumber), copy.has(initialNumber)) + Assert.notEqual(object.foo(), copy.foo()) }) test("number", () => { const num: any = 15 let copy = propDeepCopy(num) - assert.equal(num, copy) + Assert.equal(num, copy) copy += 1 - assert.notEqual(num, copy) + Assert.notEqual(num, copy) }) test("Date", () => { @@ -58,10 +58,10 @@ suite("@Prop deep copy", () => { date.setTime(date.getTime() + 5) const copy = propDeepCopy(date) - assert.equal(date.getTime(), copy.getTime()) + Assert.equal(date.getTime(), copy.getTime()) copy.setHours(12) - assert.notEqual(date.getTime(), copy.getTime()) + Assert.notEqual(date.getTime(), copy.getTime()) }) class A { @@ -92,7 +92,7 @@ suite("@Prop deep copy", () => { propCopy.inc() - assert.equal(a.num, 1) - assert.equal(propCopy.num, 2) + Assert.equal(a.num, 1) + Assert.equal(propCopy.num, 2) }) }) diff --git a/arkoala/arkui/oh-package.json5 b/arkoala/arkui/oh-package.json5 index ae5ec168c..b173c9e9c 100644 --- a/arkoala/arkui/oh-package.json5 +++ b/arkoala/arkui/oh-package.json5 @@ -40,11 +40,10 @@ "commander": "^10.0.0" }, "devDependencies": { - "@types/chai": "^4.3.1", + "@koalaui/harness": "1.4.1+devel", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", diff --git a/arkoala/arkui/package.json b/arkoala/arkui/package.json index 4e389bea4..c79e35a1a 100644 --- a/arkoala/arkui/package.json +++ b/arkoala/arkui/package.json @@ -54,14 +54,13 @@ "commander": "10.0.1" }, "devDependencies": { - "@types/chai": "^4.3.1", + "@koalaui/harness": "1.4.1+devel", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", "source-map-support": "^0.5.21" } -} \ No newline at end of file +} diff --git a/arkoala/ets-plugin/.gitlab-ci.yml b/arkoala/ets-plugin/.gitlab-ci.yml index 1f26c8b83..7c70f131e 100644 --- a/arkoala/ets-plugin/.gitlab-ci.yml +++ b/arkoala/ets-plugin/.gitlab-ci.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2024 Huawei Device Co., Ltd. +# Copyright (c) 2022-2025 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -45,6 +45,7 @@ test ets-plugin (arkoala): - build compat - build common - build ets-plugin + - build harness test ets-plugin (arkts): stage: test @@ -61,6 +62,7 @@ test ets-plugin (arkts): - build compat - build common - build ets-plugin + - build harness test ets-plugin (koala): stage: test @@ -77,6 +79,7 @@ test ets-plugin (koala): - build compat - build common - build ets-plugin + - build harness test ets-plugin (arkoala spec): stage: test @@ -94,6 +97,7 @@ test ets-plugin (arkoala spec): - build compat - build common - build ets-plugin + - build harness test ets-plugin (arkts spec): stage: test @@ -111,6 +115,7 @@ test ets-plugin (arkts spec): - build compat - build common - build ets-plugin + - build harness test ets-plugin (koala spec): stage: test @@ -128,6 +133,7 @@ test ets-plugin (koala spec): - build compat - build common - build ets-plugin + - build harness pack ets-plugin: extends: @@ -145,4 +151,4 @@ publish ets-plugin: variables: PACKAGE_DIR: arkoala/ets-plugin dependencies: - - build ets-plugin \ No newline at end of file + - build ets-plugin diff --git a/arkoala/ets-plugin/oh-package.json5 b/arkoala/ets-plugin/oh-package.json5 index 48b4bce36..430581bbe 100644 --- a/arkoala/ets-plugin/oh-package.json5 +++ b/arkoala/ets-plugin/oh-package.json5 @@ -24,11 +24,10 @@ "keywords": [], "dependencies": {}, "devDependencies": { - "@types/chai": "^4.3.1", + "@koalaui/harness": "1.4.1+devel", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", diff --git a/arkoala/ets-plugin/package.json b/arkoala/ets-plugin/package.json index 5aac259a2..3ebdbcf3a 100644 --- a/arkoala/ets-plugin/package.json +++ b/arkoala/ets-plugin/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@koalaui/ets-tsc": "4.9.5-r4", "@koalaui/memo-tsc": "4.9.5", - "@types/chai": "^4.3.1", + "@koalaui/harness": "1.4.1+devel", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", diff --git a/arkoala/ets-plugin/test/utils.ts b/arkoala/ets-plugin/test/utils.ts index b22af78f9..5f58d4926 100644 --- a/arkoala/ets-plugin/test/utils.ts +++ b/arkoala/ets-plugin/test/utils.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,7 +15,7 @@ import * as path from 'path' import * as fs from 'fs' -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import * as child from "child_process" export function testFolder(folder: string) { @@ -56,7 +56,7 @@ function assertEqualFiles(goldenFilePath: string, otherFilePath: string) { const other = fs.readFileSync(otherFilePath, "utf-8") // ignore all \r to fix testing on Windows - return assert.equal(golden.replace(/\r/g, ""), other.replace(/\r/g, "")) + return Assert.equal(golden.replace(/\r/g, ""), other.replace(/\r/g, "")) } export function collectFiles(root: string): string[] { diff --git a/arkoala/har/arkoala-har-bundle/package.json b/arkoala/har/arkoala-har-bundle/package.json index 41a133526..5cd635001 100644 --- a/arkoala/har/arkoala-har-bundle/package.json +++ b/arkoala/har/arkoala-har-bundle/package.json @@ -4,6 +4,7 @@ "@koalaui/arkoala": "../../framework", "@koalaui/arkui-common": "../../arkui-common", "@koalaui/arkoala-arkui": "../../arkui", + "@koalaui/harness": "../../../incremental/harness", "@koalaui/runtime": "../../../incremental/runtime", "@koalaui/interop": "../../../interop", "@koalaui/compat": "../../../incremental/compat", diff --git a/arkoala/package.json b/arkoala/package.json index bc4c1cd9d..0acc23501 100644 --- a/arkoala/package.json +++ b/arkoala/package.json @@ -15,6 +15,7 @@ "../incremental/build-common", "../incremental/compat", "../incremental/common", + "../incremental/harness", "../incremental/runtime", "../incremental/compiler-plugin", "../interop", @@ -28,8 +29,6 @@ "read-package-json-fast": "^3.0.2", "tslib": "^2.3.1", "ts-node": "^10.7.0", - "chai": "^4.3.6", - "@types/chai": "^4.3.1", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^13.2.0", "@rollup/plugin-replace": "^5.0.5", diff --git a/incremental/common/bridges/ohos/src/chai/index.ts b/incremental/common/bridges/ohos/src/chai/index.ts deleted file mode 100644 index b1df54150..000000000 --- a/incremental/common/bridges/ohos/src/chai/index.ts +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { expect } from "@ohos/hypium" - -export interface Assert { - (expression: any, message?: string): asserts expression - - /** - * Asserts non-strict equality (==) of actual and expected. - */ - equal(actual: T, expected: T, message?: string): void - - /** - * Asserts non-strict inequality (!=) of actual and expected. - */ - notEqual(actual: T, expected: T, message?: string): void - - /** - * Asserts strict equality (===) of actual and expected. - */ - strictEqual(actual: T, expected: T, message?: string): void - - /** - * Asserts strict inequality (!==) of actual and expected. - */ - notStrictEqual(actual: T, expected: T, message?: string): void - - deepEqual(actual: any, expected: any, message?: string): void - - notDeepEqual(actual: any, expected: any, message?: string): void - - isTrue(value: any, message?: string): void - - isFalse(value: any, message?: string): void - - closeTo(actual: number, expected: number, delta: number, message?: string): void - - fail(message?: string): void - - isNull(value: any, message?: string): void - - isNotNull(value: any, message?: string): void - - instanceOf(value: any, constructor: Function, message?: string): void - - isAtLeast(valueToCheck: number, valueToBeAtLeast: number, message?: string): void - - exists(value: any, message?: string): void - - throw(fn: () => void, message?: string): void - - throws(fn: () => void, message?: string): void - - isAbove(valueToCheck: number, valueToBeAbove: number, message?: string): void - - isBelow(valueToCheck: number, valueToBeBelow: number, message?: string): void - - match(value: string, regexp: RegExp, message?: string): void - - isDefined(value: any, message?: string): void - - isUndefined(value: any, message?: string): void - - isEmpty(object: any, message?: string): void - - isNotEmpty(object: any, message?: string): void -} - -// todo: the 'message' arg is ignored - -export var assert: Assert = ((expression: any, message?: string): void => { - expect(Boolean(expression)).assertTrue() -}) as Assert - -assert.equal = (actual: T, expected: T, message?: string): void => { - expect(actual).assertEqual(expected) -} - -assert.notEqual = (actual: T, expected: T, message?: string): void => { - // todo: not accurate impl, because compared values are not printed - expect(actual != expected).assertTrue() -} - -assert.strictEqual = (actual: T, expected: T, message?: string): void => { - // todo: not accurate impl, because compared values are not printed - expect(actual === expected).assertTrue() -} - -assert.notStrictEqual = (actual: T, expected: T, message?: string): void => { - // todo: not accurate impl, because compared values are not printed - expect(actual !== expected).assertTrue() -} - -assert.deepEqual = (actual: any, expected: any, message?: string): void => { - // todo: implement - expect(actual).assertEqual(actual/*expected*/) -} - -assert.notDeepEqual = (actual: any, expected: any, message?: string): void => { - // todo: implement - expect(actual).assertEqual(actual/*expected*/) -} - -assert.isTrue = (value: any, message?: string): void => { - expect(value).assertTrue() -} - -assert.isFalse = (value: any, message?: string): void => { - expect(value).assertFalse() -} - -assert.closeTo = (actual: number, expected: number, delta: number, message?: string): void => { - // implementation of 'assertClose' does not fit: - // expect(actual).assertClose(expected, delta) - - const diff = Math.abs(actual - expected) - if (diff == delta) - expect(diff).assertEqual(delta) - else - expect(diff).assertLess(delta) -} - -assert.fail = (message?: string): void => { - expect().assertFail() -} - -assert.isNull = (value: any, message?: string): void => { - expect(value).assertNull() -} - -assert.isNotNull = (value: any, message?: string): void => { - expect(value ? null : value).assertNull() -} - -assert.instanceOf = (value: any, constructor: Function, message?: string): void => { - // todo: not accurate impl - // expect(value).assertInstanceOf(constructor.name) - expect(value instanceof constructor).assertTrue() -} - -assert.isAtLeast = (valueToCheck: number, valueToBeAtLeast: number, message?: string): void => { - if (valueToCheck == valueToBeAtLeast) - expect(valueToCheck).assertEqual(valueToBeAtLeast) - else - expect(valueToCheck).assertLarger(valueToBeAtLeast) -} - -assert.exists = (value: any, message?: string): void => { - // todo: not accurate impl - expect(value == null).assertFalse() -} - -assert.throw = (fn: () => void, message?: string): void => { - let fnWrapper = () => { - try { - fn() - } catch (e) { - throw new Error("fn thrown exception") - } - } - expect(fnWrapper).assertThrowError("fn thrown exception") -} - -assert.throws = (fn: () => void, message?: string): void => { - assert.throw(fn, message) -} - -assert.isAbove = (valueToCheck: number, valueToBeAbove: number, message?: string): void => { - expect(valueToCheck).assertLarger(valueToBeAbove) -} - -assert.isBelow = (valueToCheck: number, valueToBeBelow: number, message?: string): void => { - expect(valueToCheck).assertLess(valueToBeBelow) -} - -assert.match = (value: string, regexp: RegExp, message?: string): void => { - // todo: not accurate impl - expect(regexp.test(value)).assertTrue() -} - -assert.isDefined = (value: any, message?: string): void => { - // todo: not accurate impl - expect(value === undefined).assertFalse() -} - -assert.isUndefined = (value: any, message?: string): void => { - expect(value).assertUndefined() -} - -assert.isEmpty = (object: any, message?: string): void => { - // todo: implement - expect(object !== undefined).assertTrue() -} - -assert.isNotEmpty = (object: any, message?: string): void => { - // todo: implement - expect(object !== undefined).assertTrue() -} - - diff --git a/incremental/common/oh-package.json5 b/incremental/common/oh-package.json5 index a217907f8..0c5455489 100644 --- a/incremental/common/oh-package.json5 +++ b/incremental/common/oh-package.json5 @@ -6,29 +6,19 @@ "types": "./index.d.ts", "files": [ "build/lib/**/*.js", - "build/lib/**/*.d.ts", - "build/bridges/ohos/**/*.js", - "build/bridges/ohos/**/*.d.ts" + "build/lib/**/*.d.ts" ], "exports": { - ".": "./build/lib/src/index.js", - "./golden": "./test/golden.js", - "./bridges": { - "ark": "./build/bridges/ohos/index.js", - "default": null - } + ".": "./build/lib/src/index.js" }, "typesVersions": { "*": { - "bridges": ["build/bridges/ohos/index.d.ts"], "*": ["build/lib/src/*", "build/lib/typescript/*"] } }, "scripts": { "compile": "tsc -b .", "clean": "rimraf build dist", - "test": "mocha", - "test:coverage": "nyc mocha", "compile:arkts": "bash ../tools/panda/arkts/arktsc --arktsconfig arktsconfig.json --ets-module" }, "keywords": [], @@ -36,15 +26,10 @@ "@koalaui/compat": "1.4.1+devel" }, "devDependencies": { - "@ohos/hypium": "^1.0.5", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", - "mocha": "^9.2.2", "source-map-support": "^0.5.21" } } diff --git a/incremental/common/package.json b/incremental/common/package.json index 4e1f4b778..5b16b2d52 100644 --- a/incremental/common/package.json +++ b/incremental/common/package.json @@ -45,15 +45,10 @@ "@koalaui/compat": "1.4.4+devel" }, "devDependencies": { - "@ohos/hypium": "1.0.6", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", - "mocha": "^9.2.2", "source-map-support": "^0.5.21" } -} \ No newline at end of file +} diff --git a/incremental/common/tsconfig.json b/incremental/common/tsconfig.json index 0e6d985ed..443ddd555 100644 --- a/incremental/common/tsconfig.json +++ b/incremental/common/tsconfig.json @@ -11,7 +11,6 @@ "include": ["./src/**/*"], "exclude": ["./src/ohos"], "references": [ - {"path": "../compat"}, - {"path": "./bridges/ohos"} + {"path": "../compat"} ] } diff --git a/incremental/compat/oh-package.json5 b/incremental/compat/oh-package.json5 index 0a0d59e03..6bb14a950 100644 --- a/incremental/compat/oh-package.json5 +++ b/incremental/compat/oh-package.json5 @@ -20,12 +20,8 @@ "dependencies": { }, "devDependencies": { - "@ohos/hypium": "1.0.6", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", diff --git a/incremental/compat/package.json b/incremental/compat/package.json index cbadced82..f60a6cdeb 100644 --- a/incremental/compat/package.json +++ b/incremental/compat/package.json @@ -32,12 +32,8 @@ "keywords": [], "dependencies": {}, "devDependencies": { - "@ohos/hypium": "1.0.6", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", diff --git a/incremental/compiler-plugin/.gitlab-ci.yml b/incremental/compiler-plugin/.gitlab-ci.yml index 415b70528..84bf44371 100644 --- a/incremental/compiler-plugin/.gitlab-ci.yml +++ b/incremental/compiler-plugin/.gitlab-ci.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023 Huawei Device Co., Ltd. +# Copyright (c) 2022-2025 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -49,6 +49,7 @@ test compiler-plugin: - build compat - build common - build compiler-plugin + - build harness pack compiler-plugin: extends: diff --git a/incremental/compiler-plugin/oh-package.json5 b/incremental/compiler-plugin/oh-package.json5 index 40e5742a6..3281df8ed 100644 --- a/incremental/compiler-plugin/oh-package.json5 +++ b/incremental/compiler-plugin/oh-package.json5 @@ -13,11 +13,10 @@ "@koalaui/common": "1.4.1+devel" }, "devDependencies": { - "@types/chai": "^4.3.1", + "@koalaui/harness": "1.4.1+devel", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", diff --git a/incremental/compiler-plugin/package.json b/incremental/compiler-plugin/package.json index 146106309..75394838e 100644 --- a/incremental/compiler-plugin/package.json +++ b/incremental/compiler-plugin/package.json @@ -13,11 +13,10 @@ "@koalaui/common": "1.4.4+devel" }, "devDependencies": { - "@types/chai": "^4.3.1", + "@koalaui/harness": "1.4.1+devel", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", @@ -31,4 +30,4 @@ "test:coverage": "nyc mocha", "canonize": "npm run unmemoize:tests && cp -r ./test/unmemoized/examples ./test/golden/" } -} \ No newline at end of file +} diff --git a/incremental/compiler-plugin/test/auto_detect.test.ts b/incremental/compiler-plugin/test/auto_detect.test.ts index ea6993fa6..be1c16032 100644 --- a/incremental/compiler-plugin/test/auto_detect.test.ts +++ b/incremental/compiler-plugin/test/auto_detect.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,7 +15,7 @@ import { Context } from "./context.test" import { log, getLogFiltered, resetLog } from "./util.test" -import { assert } from "chai" +import { Assert } from "@koalaui/harness" const someId = "xxx" @@ -34,7 +34,7 @@ suite("Deduce annotation by assignment destination", () => { q = p(17) } start(new Context(), someId) - assert.equal(q, 18) + Assert.equal(q, 18) }) test("Direct initializer of annotated variable v.2", () => { @@ -50,7 +50,7 @@ suite("Deduce annotation by assignment destination", () => { q = p(17, ()=>{}) } start(new Context(), someId) - assert.equal(q, 18) + Assert.equal(q, 18) }) test("Direct argument of annotated parameter", () => { @@ -70,6 +70,6 @@ suite("Deduce annotation by assignment destination", () => { }) } start(new Context(), someId) - assert.equal(getLogFiltered(), "got: 17\n") + Assert.equal(getLogFiltered(), "got: 17\n") }) }) diff --git a/incremental/compiler-plugin/test/basic.test.ts b/incremental/compiler-plugin/test/basic.test.ts index 26d9b039a..3935f24da 100644 --- a/incremental/compiler-plugin/test/basic.test.ts +++ b/incremental/compiler-plugin/test/basic.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -19,7 +19,7 @@ import defexport from "./default-export.test" import { E } from "./export-as.test" import { __id, Context } from "./context.test" import { log, getLogFiltered, resetLog, cleanDumpDirectory, checkDump } from "./util.test" -import { assert } from "chai" +import { Assert } from "@koalaui/harness" const someId = "xxx" @@ -37,7 +37,7 @@ suite("Function and callsite transformations", () => { } start(new Context(), someId) - assert.equal( + Assert.equal( getLogFiltered(), `I'm zex wax lambda @@ -55,7 +55,7 @@ wax lambda } start(new Context(), someId) - assert.equal( + Assert.equal( getLogFiltered(), `I'm til, I have an apple til lambda @@ -74,7 +74,7 @@ til lambda } start(new Context(), someId) - assert.equal( + Assert.equal( getLogFiltered(), `I'm accessId, I have an orange accessId lambda, I have an id on the call site: xxx5___key_id_DIRNAME/basic.test.ts7___key_id_DIRNAME/module.test.ts @@ -93,7 +93,7 @@ accessId lambda, I have an id on the call site: xxx5___key_id_DIRNAME/basic.test } start(new Context(), someId) - assert.equal( + Assert.equal( getLogFiltered(), `I'm qox, I have an apple qox lambda @@ -112,7 +112,7 @@ qox lambda } start(new Context(), someId) - assert.equal( + Assert.equal( getLogFiltered(), `I'm juv, I have a plum ` @@ -129,7 +129,7 @@ qox lambda } start(new Context(), someId) - assert.equal( + Assert.equal( getLogFiltered(), `I'm rek the member. I have an avocado ` @@ -148,7 +148,7 @@ qox lambda } start(new Context(), someId) - assert.equal( + Assert.equal( getLogFiltered(), `I'm cep the member. I have an onion, false cep lambda @@ -165,7 +165,7 @@ cep lambda } start(new Context(), someId) - assert.equal( + Assert.equal( getLogFiltered(), `funny ui cow thoughtfully jumps happy non-ui goat thoughtfully jumps @@ -182,7 +182,7 @@ happy non-ui goat thoughtfully jumps } start(new Context(), someId) - assert.equal( + Assert.equal( getLogFiltered(), `funny ui pig brainlessly dances happy non-ui horse brainlessly dances @@ -198,7 +198,7 @@ happy non-ui horse brainlessly dances x = exported(17) } start(new Context(), someId) - assert.equal(x, 18) + Assert.equal(x, 18) }) test("The real declaration is discoverable through export as", () => { @@ -209,7 +209,7 @@ happy non-ui horse brainlessly dances x = E(17) } start(new Context(), someId) - assert.equal(x, 18) + Assert.equal(x, 18) }) @@ -221,7 +221,7 @@ happy non-ui horse brainlessly dances x = defexport() } start(new Context(), someId) - assert.equal(x, 29) + Assert.equal(x, 29) }) for (let name of ["accessId", "cep", "juv", "qox", "rek", "til", "zex", "kla", "ryq", "bae"]) { diff --git a/incremental/compiler-plugin/test/diagnostics/diagnostics.test.ts b/incremental/compiler-plugin/test/diagnostics/diagnostics.test.ts index e1236265e..67a027c0e 100644 --- a/incremental/compiler-plugin/test/diagnostics/diagnostics.test.ts +++ b/incremental/compiler-plugin/test/diagnostics/diagnostics.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import * as child from "child_process" import * as path from "path" @@ -26,7 +26,7 @@ function diagnostics(name: string, file: string, message: string) { test(name, () => { const result = runTsc(file) console.log(result.stdout.toString()) - assert.include(result.stdout.toString(), message) + Assert.include(result.stdout.toString(), message) }).timeout(30000); } @@ -34,8 +34,8 @@ function noDiagnostics(name: string, file: string, message: string) { test(name, () => { const result = runTsc(file) console.log(result.stdout.toString()) - assert.notInclude(result.stderr.toString(), message) - assert.notInclude(result.stdout.toString(), message) + Assert.notInclude(result.stderr.toString(), message) + Assert.notInclude(result.stdout.toString(), message) }).timeout(30000); } diff --git a/incremental/compiler-plugin/test/function_return.test.ts b/incremental/compiler-plugin/test/function_return.test.ts index 69b219597..1a5d2f65e 100644 --- a/incremental/compiler-plugin/test/function_return.test.ts +++ b/incremental/compiler-plugin/test/function_return.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -14,7 +14,7 @@ */ import { Context } from "./context.test" -import { assert } from "chai" +import { Assert } from "@koalaui/harness" const someId = "xxx" @@ -74,7 +74,7 @@ suite("Returning memo functions", () => { result = wrapper() } start(new Context(), someId) - assert.equal(result, "17") + Assert.equal(result, "17") }) test("Memo lambda id", () => { @@ -84,7 +84,7 @@ suite("Returning memo functions", () => { result = runner(changer(foo())) } start(new Context(), someId) - assert.equal(result, "119") + Assert.equal(result, "119") }) }) diff --git a/incremental/compiler-plugin/test/parenthesised-call.test.ts b/incremental/compiler-plugin/test/parenthesised-call.test.ts index ec35ac296..bdd344234 100644 --- a/incremental/compiler-plugin/test/parenthesised-call.test.ts +++ b/incremental/compiler-plugin/test/parenthesised-call.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,7 +15,6 @@ // import { Context } from "./context.test" import { checkDump } from "./util.test" -// import { assert } from "chai" // const someId: string = "xxx" diff --git a/incremental/compiler-plugin/test/util.test.ts b/incremental/compiler-plugin/test/util.test.ts index d0f1f469c..dad7ca225 100644 --- a/incremental/compiler-plugin/test/util.test.ts +++ b/incremental/compiler-plugin/test/util.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,7 +15,7 @@ import * as path from "path" import * as fs from "fs" -import { assert } from "chai" +import { Assert } from "@koalaui/harness" let logText: string = "" @@ -60,6 +60,6 @@ export function checkDump(name: string, module: string) { const test = loadDump(name, "dump", module) const testFiltered = test ? filterDirname(test) : undefined const golden = loadDump(name, "golden", module) - assert.equal(golden, testFiltered) + Assert.equal(golden, testFiltered) }) } diff --git a/incremental/compiler-plugin/test/variable_declaration.test.ts b/incremental/compiler-plugin/test/variable_declaration.test.ts index c97d223bd..e38880be7 100644 --- a/incremental/compiler-plugin/test/variable_declaration.test.ts +++ b/incremental/compiler-plugin/test/variable_declaration.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,7 +15,6 @@ import { Context } from "./context.test" import { checkDump } from "./util.test" -import { assert } from "chai" const someId = "xxx" diff --git a/incremental/compiler-plugin/test/whole_file.test.ts b/incremental/compiler-plugin/test/whole_file.test.ts index fe813a0e1..fe2657fcd 100644 --- a/incremental/compiler-plugin/test/whole_file.test.ts +++ b/incremental/compiler-plugin/test/whole_file.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -14,12 +14,12 @@ */ import * as fs from "fs" -import { assert } from "chai" +import { Assert } from "@koalaui/harness" function assertUnmemoizedEqualGolden(filename: string) { const unmemoized = fs.readFileSync(`./test/unmemoized/${filename}`, "utf8"); const golden = fs.readFileSync(`./test/golden/${filename}`, "utf8"); - return assert.equal(golden, unmemoized) + return Assert.equal(golden, unmemoized) } suite("Memo stable", () => { diff --git a/incremental/compiler-plugin/test/wrap_object_literal.test.ts b/incremental/compiler-plugin/test/wrap_object_literal.test.ts index 24ea67b2d..37e960cbd 100644 --- a/incremental/compiler-plugin/test/wrap_object_literal.test.ts +++ b/incremental/compiler-plugin/test/wrap_object_literal.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,7 +15,7 @@ import { Context } from "./context.test" import { checkDump } from "./util.test" -import { assert } from "chai" +import { Assert } from "@koalaui/harness" const someId = "xxx" @@ -32,8 +32,8 @@ suite("Wrapping literals in compute", () => { result = foo({x: 17, y: 19}) } start(new Context(), someId) - assert.equal(result.x, 17) - assert.equal(result.y, 19) + Assert.equal(result.x, 17) + Assert.equal(result.y, 19) }) test("Arrow function is wrapped in compute", () => { @@ -47,7 +47,7 @@ suite("Wrapping literals in compute", () => { arrowFunctionReceiver(() => {}) } start(new Context(), someId) - assert.equal(counter, 1) + Assert.equal(counter, 1) }) diff --git a/incremental/compiler-plugin/tsconfig.json b/incremental/compiler-plugin/tsconfig.json index bb9c6b536..1dec59682 100644 --- a/incremental/compiler-plugin/tsconfig.json +++ b/incremental/compiler-plugin/tsconfig.json @@ -7,6 +7,7 @@ }, "include": ["src/**/*"], "references": [ + { "path": "../harness" }, { "path": "../compat" }, { "path": "../common" } ] diff --git a/incremental/harness/.gitlab-ci.yml b/incremental/harness/.gitlab-ci.yml new file mode 100644 index 000000000..6009979fd --- /dev/null +++ b/incremental/harness/.gitlab-ci.yml @@ -0,0 +1,33 @@ +# Copyright (c) 2022-2025 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +build harness: + stage: build + interruptible: true + extends: .linux-vm-shell-task + before_script: + - !reference [.setup, script] + - cd incremental/harness + script: + - npm run compile:all + needs: + - install node modules (arkoala) + - install node modules (arkoala-arkts) + - install node modules (incremental) + - install node modules (interop) + - build compat + - build common + artifacts: + expire_in: 2 days + paths: + - incremental/harness/build diff --git a/incremental/harness/README.md b/incremental/harness/README.md new file mode 100644 index 000000000..97118e43c --- /dev/null +++ b/incremental/harness/README.md @@ -0,0 +1 @@ +This is a harness library compatible with OHOS and ArkTS. diff --git a/incremental/harness/arktsconfig.json b/incremental/harness/arktsconfig.json new file mode 100644 index 000000000..cb1ea1f99 --- /dev/null +++ b/incremental/harness/arktsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": "./src", + "outDir": "build/abc", + "paths": { + "@koalaui/common": ["../common/src"], + "@koalaui/compat": ["../compat/src/arkts"], + "#harness": ["./src/arkts"] + } + }, + "include": ["./src/arkts/**/*"] +} diff --git a/incremental/common/test/golden.js b/incremental/harness/js/golden.js similarity index 96% rename from incremental/common/test/golden.js rename to incremental/harness/js/golden.js index 42298bd34..52c48305e 100644 --- a/incremental/common/test/golden.js +++ b/incremental/harness/js/golden.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/incremental/harness/oh-package.json5 b/incremental/harness/oh-package.json5 new file mode 100644 index 000000000..f3c34e272 --- /dev/null +++ b/incremental/harness/oh-package.json5 @@ -0,0 +1,37 @@ +{ + "name": "@koalaui/harness", + "version": "1.4.1+devel", + "description": "A harness library compatible with OHOS and ArkTS", + "main": "build/typescript/index.js", + "types": "build/typescript/index.d.ts", + "files": [ + "js/golden.js", + "build/typescript/**/*.js", + "build/typescript/**/*.d.ts" + ], + "exports": { + "./golden": "./js/golden.js", + ".": "./build/typescript/index.js" + }, + "scripts": { + "compile": "tsc -b .", + "clean": "rimraf build dist" + }, + "keywords": [], + "dependencies": { + "@koalaui/common": "1.4.1+devel" + }, + "devDependencies": { + "@ohos/hypium": "1.0.6", + "@types/chai": "^4.3.1", + "@types/mocha": "^9.1.0", + "@typescript-eslint/eslint-plugin": "^5.20.0", + "@typescript-eslint/parser": "^5.20.0", + "chai": "^4.3.6", + "eslint": "^8.13.0", + "eslint-plugin-unused-imports": "^2.0.0", + "mocha": "^9.2.2", + "rimraf": "^3.0.2", + "source-map-support": "^0.5.21" + } +} diff --git a/incremental/harness/package.json b/incremental/harness/package.json new file mode 100644 index 000000000..fca850969 --- /dev/null +++ b/incremental/harness/package.json @@ -0,0 +1,49 @@ +{ + "name": "@koalaui/harness", + "version": "1.4.1+devel", + "description": "A harness library compatible with OHOS and ArkTS", + "main": "build/src/index.js", + "types": "build/src/index.d.ts", + "files": [ + "js/golden.js", + "build/src/**/*.js", + "build/src/**/*.d.ts" + ], + "imports": { + "#harness": { + "ark": "./build/src/ohos/index.js", + "ios": "./build/src/typescript/index.js", + "browser": "./build/src/typescript/index.js", + "node": "./build/src/typescript/index.js", + "default": "./build/src/typescript/index.js" + } + }, + "exports": { + "./golden": "./js/golden.js", + ".": "./build/src/index.js" + }, + "scripts": { + "compile": "tsc -b .", + "compile:ohos": "tsc -b ./tsconfig-ohos.json", + "compile:arkts": "bash ../tools/panda/arkts/arktsc --arktsconfig arktsconfig.json --ets-module", + "compile:all": "npm run compile && npm run compile:ohos", + "clean": "rimraf build" + }, + "keywords": [], + "dependencies": { + "@koalaui/common": "1.4.1+devel" + }, + "devDependencies": { + "@ohos/hypium": "1.0.6", + "@types/chai": "^4.3.1", + "@types/mocha": "^9.1.0", + "@typescript-eslint/eslint-plugin": "^5.20.0", + "@typescript-eslint/parser": "^5.20.0", + "chai": "^4.3.6", + "eslint": "^8.13.0", + "eslint-plugin-unused-imports": "^2.0.0", + "mocha": "^9.2.2", + "rimraf": "^3.0.2", + "source-map-support": "^0.5.21" + } +} diff --git a/incremental/runtime/test-arkts/harness.ts b/incremental/harness/src/arkts/chai.ts similarity index 86% rename from incremental/runtime/test-arkts/harness.ts rename to incremental/harness/src/arkts/chai.ts index a4340899a..a8722d72d 100644 --- a/incremental/runtime/test-arkts/harness.ts +++ b/incremental/harness/src/arkts/chai.ts @@ -1,27 +1,27 @@ -import { int32 } from "@koalaui/common" +/* + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -// TODO: need to mimic to "chai" -class AnAssertionError extends Error { - // public name: string - // public showDiff: boolean - // public stack: string - constructor(message: string) { - super(message) - } - toString(): string { - let text = "Assert: " + this.message - return text - } -} +import { int32 } from "@koalaui/common" -// TODO: should be compatible with "chai" export class Assert { /** * Throws a failure. * @param message - message to display on error */ - static fail(message?: string): void { - throw new AnAssertionError(message ?? "Assert.fail") + static fail(message?: string): never { + throw new AssertionError(message ?? "Assert.fail") } /** @@ -223,8 +223,8 @@ export class Assert { * @param value - actual value * @param message - message to display on error */ - static isNull(value: T | null, message?: string): void { - if (value === null) return + static isNull(value: T, message?: string): void { + if (value == null) return // replace with '===' when panda-issue-19588 is fixed Assert.fail(message ?? `actual '${value}' is not null unexpectedly`) } @@ -233,8 +233,8 @@ export class Assert { * @param value - actual value * @param message - message to display on error */ - static isNotNull(value: T | null, message?: string): void { - if (value !== null) return + static isNotNull(value: T, message?: string): void { + if (value != null) return // replace with '!==' when panda-issue-19588 is fixed Assert.fail(message ?? `actual '${value}' is null unexpectedly`) } @@ -265,8 +265,8 @@ export class Assert { * @param value - actual value * @param message - message to display on error */ - static exists(value?: T | null, message?: string): void { - if (value === undefined || value === null) return + static exists(value: T, message?: string): void { + if (value == undefined || value == null) return // replace with '===' when panda-issue-19588 is fixed Assert.fail(message ?? `actual '${value}' does not exist unexpectedly`) } @@ -275,8 +275,8 @@ export class Assert { * @param value - actual value * @param message - message to display on error */ - static notExists(value?: T | null, message?: string): void { - if (value !== undefined && value !== null) return + static notExists(value: T, message?: string): void { + if (value != undefined && value != null) return // replace with '!==' when panda-issue-19588 is fixed Assert.fail(message ?? `actual '${value}' exists unexpectedly`) } @@ -285,8 +285,8 @@ export class Assert { * @param value - actual value * @param message - message to display on error */ - static isUndefined(value?: T, message?: string): void { - if (value === undefined) return + static isUndefined(value: T, message?: string): void { + if (value == undefined) return // replace with '===' when panda-issue-19588 is fixed Assert.fail(message ?? `actual '${value}' is defined unexpectedly`) } @@ -295,8 +295,8 @@ export class Assert { * @param value - actual value * @param message - message to display on error */ - static isDefined(value?: T, message?: string): void { - if (value !== undefined) return + static isDefined(value: T, message?: string): void { + if (value != undefined) return // replace with '!==' when panda-issue-19588 is fixed Assert.fail(message ?? `actual '${value}' is undefined unexpectedly`) } @@ -610,19 +610,12 @@ export class Assert { static throw(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string): void;*/ /** - * Asserts that fn will throw an error. - * - * @param fn Function that may throw. - * @param errorLike Expected error constructor or error instance. - * @param errMsgMatcher Expected error message matcher. - * @param message Message to display on error. + * Asserts that the given function will throw an error. + * @param func - a function that may throw an error * / - static throw( - fn: () => void, - errorLike?: ErrorConstructor | Error | null, - errMsgMatcher?: RegExp | string | null, - message?: string, - ): void;*/ + static throw(func: () => void): void { + Assert.throws(func) + } /** * Asserts that the given function will throw an error. @@ -776,79 +769,6 @@ export class Assert { } } -const stack = new Array() - -function getCurrentSuite(): Suite | undefined { - const index = stack.length - 1 - if (index < 0) return undefined - const test = stack[index] - if (test instanceof Suite) return test as Suite - throw new Error("test is already running") -} - -class Test { - public error: Error | undefined = undefined - readonly name: string - readonly content: () => void - constructor(name: string, content?: () => void) { - this.name = name - this.content = () => { - const suite = getCurrentSuite() - try { - stack.push(this) - content?.() - } catch (error) { - this.error = error as Error - console.log(this.error!.toString()) - console.log(this.error!.stack) - } finally { - stack.pop() - console.log(" ".repeat(stack.length) + this.toString()) - if (suite) suite.tests.push(this) - else if (!this.passed) throw new Error("TEST FAILED") - } - } - } - get passed(): boolean { - return this.error === undefined - } - toString(): string { - let result = this.passed ? "passed" : "failed" - return `${result} test: "${this.name}"` - } -} - -class Suite extends Test { - readonly tests = new Array() - constructor(name: string, content?: () => void) { - super(name, content) - } - private get failures(): int32 { - let failed = 0 - for (let i = 0; i < this.tests.length; i++) { - const test = this.tests[i] - if (!test.passed) failed++ - } - return failed - } - override get passed(): boolean { - return super.passed && this.failures == 0 - } - override toString(): string { - const failures = this.failures - const result = failures > 0 ? `failed tests '${failures}' in` : this.error ? "failed" : "passed" - return `${result} suite: "${this.name}"` - } -} - -export function test(name: string, content?: () => void) { - new Test(name, content).content() -} - -export function suite(name: string, content?: () => void) { - new Suite(name, content).content() -} - function equalArrayContent(actual: Array, expected: Array): boolean { const length = actual.length if (expected.length != length) return false diff --git a/incremental/common/bridges/ohos/src/index.ts b/incremental/harness/src/arkts/index.ts similarity index 78% rename from incremental/common/bridges/ohos/src/index.ts rename to incremental/harness/src/arkts/index.ts index 17641d36d..546f0878a 100644 --- a/incremental/common/bridges/ohos/src/index.ts +++ b/incremental/harness/src/arkts/index.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,5 @@ * limitations under the License. */ -export {startTests} from "./mocha" -export * from "./chai" - -import "./mocha" // globals +export { Assert } from "./chai" +export { startTests, suite, suiteSetup, test } from "./mocha" diff --git a/incremental/harness/src/arkts/mocha.ts b/incremental/harness/src/arkts/mocha.ts new file mode 100644 index 000000000..a7e575ed9 --- /dev/null +++ b/incremental/harness/src/arkts/mocha.ts @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { int32 } from "@koalaui/common" + +const stack = new Array() + +function getCurrentSuite(): Suite | undefined { + const index = stack.length - 1 + if (index < 0) return undefined + const test = stack[index] + if (test instanceof Suite) return test as Suite + throw new Error("test is already running") +} + +class Test { + public error: Error | undefined = undefined + readonly name: string + readonly content: () => void + constructor(name: string, content?: () => void) { + this.name = name + this.content = () => { + const suite = getCurrentSuite() + try { + stack.push(this) + content?.() + } catch (error) { + this.error = error as Error + console.log(this.error!.toString()) + } finally { + stack.pop() + console.log(" ".repeat(stack.length) + this.toString()) + if (suite) suite.tests.push(this) + else if (!this.passed) throw new Error("TEST FAILED") + } + } + } + get passed(): boolean { + return this.error === undefined + } + toString(): string { + let result = this.passed ? "passed" : "failed" + return `${result} test: "${this.name}"` + } +} + +class Suite extends Test { + readonly tests = new Array() + constructor(name: string, content?: () => void) { + super(name, content) + } + private get failures(): int32 { + let failed = 0 + for (let i = 0; i < this.tests.length; i++) { + const test = this.tests[i] + if (!test.passed) failed++ + } + return failed + } + override get passed(): boolean { + return super.passed && this.failures == 0 + } + override toString(): string { + const failures = this.failures + const result = failures > 0 ? `failed tests '${failures}' in` : this.error ? "failed" : "passed" + return `${result} suite: "${this.name}"` + } +} + +export function test(name: string, content?: () => void) { + new Test(name, content).content() +} + +export function suite(name: string, content?: () => void) { + new Suite(name, content).content() +} + +export function suiteSetup(name: string, content?: () => void) { + throw new Error("unsupported suiteSetup: " + name) +} + +export function startTests(generateGolden: boolean = false) { + throw new Error("unsupported startTests: " + generateGolden) +} diff --git a/incremental/harness/src/index.ts b/incremental/harness/src/index.ts new file mode 100644 index 000000000..992ddac32 --- /dev/null +++ b/incremental/harness/src/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { Assert, startTests, suite, suiteSetup, test } from "#harness" diff --git a/incremental/harness/src/ohos/chai.ts b/incremental/harness/src/ohos/chai.ts new file mode 100644 index 000000000..734e55342 --- /dev/null +++ b/incremental/harness/src/ohos/chai.ts @@ -0,0 +1,782 @@ +/* + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from "@ohos/hypium" + +export class Assert { + /** + * Throws a failure. + * @param message - message to display on error + */ + static fail(message?: string): never { + expect().assertFail() + throw new Error("OHOS failed: " + message) + } + + /** + * Asserts that object is truthy. + * + * T Type of object. + * @param object Object to test. + * @param message Message to display on error. + */ + static isOk(value: T, message?: string): void { + Assert.fail("Assert.isOk unsupported") + } + + /** + * Asserts that object is truthy. + * + * T Type of object. + * @param object Object to test. + * @param message Message to display on error. + */ + static ok(value: T, message?: string): void { + Assert.fail("Assert.ok unsupported") + } + + /** + * Asserts that object is falsy. + * + * T Type of object. + * @param object Object to test. + * @param message Message to display on error. + */ + static isNotOk(value: T, message?: string): void { + Assert.fail("Assert.isNotOk unsupported") + } + + /** + * Asserts that object is falsy. + * + * T Type of object. + * @param object Object to test. + * @param message Message to display on error. + */ + static notOk(value: T, message?: string): void { + Assert.fail("Assert.notOk unsupported") + } + + /** + * Asserts non-strict equality (==) of actual and expected. + * @param actual - actual value + * @param expected - potential expected value + * @param message - message to display on error + */ + static equal(actual: T, expected: T, message?: string): void { + expect(actual).assertEqual(expected) + } + + /** + * Asserts non-strict inequality (!=) of actual and expected. + * @param actual - actual value + * @param expected - potential expected value + * @param message - message to display on error + */ + static notEqual(actual: T, expected: T, message?: string): void { + // todo: not accurate impl, because compared values are not printed + expect(actual != expected).assertTrue() + } + + /** + * Asserts strict equality (===) of actual and expected. + * @param actual - actual value + * @param expected - potential expected value + * @param message - message to display on error + */ + static strictEqual(actual: T, expected: T, message?: string): void { + // todo: not accurate impl, because compared values are not printed + expect(actual === expected).assertTrue() + } + + /** + * Asserts strict inequality (!==) of actual and expected. + * @param actual - actual value + * @param expected - potential expected value + * @param message - message to display on error + */ + static notStrictEqual(actual: T, expected: T, message?: string): void { + // todo: not accurate impl, because compared values are not printed + expect(actual !== expected).assertTrue() + } + + /** + * Asserts that actual is deeply equal to expected. + * @param actual - actual value array + * @param expected - potential expected value array + * @param message - message to display on error + */ + static deepEqual(actual: Array, expected: Array, message?: string): void { + // todo: implement + expect(actual).assertEqual(actual/*expected*/) + } + + /** + * Asserts that actual is not deeply equal to expected. + * @param actual - actual value array + * @param expected - potential expected value array + * @param message - message to display on error + */ + static notDeepEqual(actual: Array, expected: Array, message?: string): void { + // todo: implement + expect(actual).assertEqual(actual/*expected*/) + } + + /** + * Asserts valueToCheck is strictly greater than (>) valueToBeAbove. + * + * @param valueToCheck Actual value. + * @param valueToBeAbove Minimum Potential expected value. + * @param message Message to display on error. + */ + static isAbove(valueToCheck: number, valueToBeAbove: number, message?: string): void { + expect(valueToCheck).assertLarger(valueToBeAbove) + } + + /** + * Asserts valueToCheck is greater than or equal to (>=) valueToBeAtLeast. + * + * @param valueToCheck Actual value. + * @param valueToBeAtLeast Minimum Potential expected value. + * @param message Message to display on error. + */ + static isAtLeast(valueToCheck: number, valueToBeAtLeast: number, message?: string): void { + if (valueToCheck == valueToBeAtLeast) + expect(valueToCheck).assertEqual(valueToBeAtLeast) + else + expect(valueToCheck).assertLarger(valueToBeAtLeast) + } + + /** + * Asserts valueToCheck is strictly less than (<) valueToBeBelow. + * + * @param valueToCheck Actual value. + * @param valueToBeBelow Minimum Potential expected value. + * @param message Message to display on error. + */ + static isBelow(valueToCheck: number, valueToBeBelow: number, message?: string): void { + expect(valueToCheck).assertLess(valueToBeBelow) + } + + /** + * Asserts valueToCheck is less than or equal to (<=) valueToBeAtMost. + * + * @param valueToCheck Actual value. + * @param valueToBeAtMost Minimum Potential expected value. + * @param message Message to display on error. + */ + static isAtMost(valueToCheck: number, valueToBeAtMost: number, message?: string): void { + Assert.fail("Assert.isAtMost unsupported") + } + + /** + * Asserts that value is true. + * @param value - actual value + * @param message - message to display on error + */ + static isTrue(value?: boolean, message?: string): void { + expect(value).assertTrue() + } + + /** + * Asserts that value is not true. + * @param value - actual value + * @param message - message to display on error + */ + static isNotTrue(value?: boolean, message?: string): void { + if (value != true) return + Assert.fail(message ?? `actual '${value}' is true unexpectedly`) + } + + /** + * Asserts that value is false. + * @param value - actual value + * @param message - message to display on error + */ + static isFalse(value?: boolean, message?: string): void { + expect(value).assertFalse() + } + + /** + * Asserts that value is not false. + * @param value - actual value + * @param message - message to display on error + */ + static isNotFalse(value?: boolean, message?: string): void { + if (value != false) return + Assert.fail(message ?? `actual '${value}' is false unexpectedly`) + } + + /** + * Asserts that value is null. + * @param value - actual value + * @param message - message to display on error + */ + static isNull(value: T | null, message?: string): void { + expect(value).assertNull() + } + + /** + * Asserts that value is not null. + * @param value - actual value + * @param message - message to display on error + */ + static isNotNull(value: T | null, message?: string): void { + expect(value ? null : value).assertNull() + } + + /** + * Asserts that value is NaN. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isNaN(value: T, message?: string): void { + Assert.fail("Assert.isNaN unsupported") + } + + /** + * Asserts that value is not NaN. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isNotNaN(value: T, message?: string): void { + Assert.fail("Assert.isNotNaN unsupported") + } + + /** + * Asserts that the target is neither null nor undefined. + * @param value - actual value + * @param message - message to display on error + */ + static exists(value?: T | null, message?: string): void { + // todo: not accurate impl + expect(value == null).assertFalse() + } + + /** + * Asserts that the target is either null or undefined. + * @param value - actual value + * @param message - message to display on error + */ + static notExists(value?: T | null, message?: string): void { + if (value !== undefined && value !== null) return + Assert.fail(message ?? `actual '${value}' exists unexpectedly`) + } + + /** + * Asserts that value is undefined. + * @param value - actual value + * @param message - message to display on error + */ + static isUndefined(value?: T, message?: string): void { + expect(value).assertUndefined() + } + + /** + * Asserts that value is not undefined. + * @param value - actual value + * @param message - message to display on error + */ + static isDefined(value?: T, message?: string): void { + // todo: not accurate impl + expect(value === undefined).assertFalse() + } + + /** + * Asserts that value is a function. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isFunction(value: T, message?: string): void { + Assert.fail("Assert.isFunction unsupported") + } + + /** + * Asserts that value is not a function. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isNotFunction(value: T, message?: string): void { + Assert.fail("Assert.isNotFunction unsupported") + } + + /** + * Asserts that value is an object of type 'Object' + * (as revealed by Object.prototype.toString). + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + * @remarks The assertion does not match subclassed objects. + */ + static isObject(value: T, message?: string): void { + Assert.fail("Assert.isObject unsupported") + } + + /** + * Asserts that value is not an object of type 'Object' + * (as revealed by Object.prototype.toString). + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isNotObject(value: T, message?: string): void { + Assert.fail("Assert.isNotObject unsupported") + } + + /** + * Asserts that value is an array. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isArray(value: T, message?: string): void { + Assert.fail("Assert.isArray unsupported") + } + + /** + * Asserts that value is not an array. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isNotArray(value: T, message?: string): void { + Assert.fail("Assert.isNotArray unsupported") + } + + /** + * Asserts that value is a string. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isString(value: T, message?: string): void { + Assert.fail("Assert.isString unsupported") + } + + /** + * Asserts that value is not a string. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isNotString(value: T, message?: string): void { + Assert.fail("Assert.isNotString unsupported") + } + + /** + * Asserts that value is a number. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isNumber(value: T, message?: string): void { + Assert.fail("Assert.isNumber unsupported") + } + + /** + * Asserts that value is not a number. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isNotNumber(value: T, message?: string): void { + Assert.fail("Assert.isNotNumber unsupported") + } + + /** + * Asserts that value is a finite number. + * Unlike `.isNumber`, this will fail for `NaN` and `Infinity`. + * + * T Type of value + * @param value Actual value + * @param message Message to display on error. + */ + static isFinite(value: T, message?: string): void { + Assert.fail("Assert.isFinite unsupported") + } + + /** + * Asserts that value is a boolean. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isBoolean(value: T, message?: string): void { + Assert.fail("Assert.isBoolean unsupported") + } + + /** + * Asserts that value is not a boolean. + * + * T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + static isNotBoolean(value: T, message?: string): void { + Assert.fail("Assert.isNotBoolean unsupported") + } + + /** + * Asserts that value's type is name, as determined by Object.prototype.toString. + * + * T Type of value. + * @param value Actual value. + * @param name Potential expected type name of value. + * @param message Message to display on error. + */ + static typeOf(value: T, name: string, message?: string): void { + Assert.fail("Assert.typeOf unsupported") + } + + /** + * Asserts that value's type is not name, as determined by Object.prototype.toString. + * + * T Type of value. + * @param value Actual value. + * @param name Potential expected type name of value. + * @param message Message to display on error. + */ + static notTypeOf(value: T, name: string, message?: string): void { + Assert.fail("Assert.notTypeOf unsupported") + } + + /** + * Asserts that value is an instance of constructor. + * + * T Type of value. + * @param value Actual value. + * @param construct Potential expected contructor of value. + * @param message Message to display on error. + */ + static instanceOf(value: T, construct: Function, message?: string): void { + // todo: not accurate impl + // expect(value).assertInstanceOf(construct.name) + expect(value instanceof construct).assertTrue() + } + + /** + * Asserts that value is not an instance of constructor. + * + * T Type of value. + * @param value Actual value. + * @param constructor Potential expected contructor of value. + * @param message Message to display on error. + * / + static notInstanceOf(value: T, type: Function, message?: string): void*/ + + /** + * Asserts that haystack includes needle. + * + * @param haystack Container string. + * @param needle Potential substring of haystack. + * @param message Message to display on error. + * / + static include(haystack: string, needle: string, message?: string): void*/ + + /** + * Asserts that haystack includes needle. + * + * T Type of values in haystack. + * @param haystack Container array, set or map. + * @param needle Potential value contained in haystack. + * @param message Message to display on error. + * / + static include( + haystack: ReadonlyArray | ReadonlySet | ReadonlyMap, + needle: T, + message?: string, + ): void;*/ + + /** + * Asserts that haystack includes needle. + * + * T Type of values in haystack. + * @param haystack WeakSet container. + * @param needle Potential value contained in haystack. + * @param message Message to display on error. + * / + static include(haystack: WeakSet, needle: T, message?: string): void;*/ + + /** + * Asserts that haystack includes needle. + * + * T Type of haystack. + * @param haystack Object. + * @param needle Potential subset of the haystack's properties. + * @param message Message to display on error. + * / + static include(haystack: T, needle: Partial, message?: string): void;*/ + + /** + * Asserts that haystack does not includes needle. + * + * @param haystack Container string. + * @param needle Potential substring of haystack. + * @param message Message to display on error. + * / + static notInclude(haystack: string, needle: string, message?: string): void;*/ + + /** + * Asserts that haystack does not includes needle. + * + * T Type of values in haystack. + * @param haystack Container array, set or map. + * @param needle Potential value contained in haystack. + * @param message Message to display on error. + * / + static notInclude( + haystack: ReadonlyArray | ReadonlySet | ReadonlyMap, + needle: T, + message?: string, + ): void;*/ + + /** + * Asserts that haystack does not includes needle. + * + * T Type of values in haystack. + * @param haystack WeakSet container. + * @param needle Potential value contained in haystack. + * @param message Message to display on error. + * / + static notInclude(haystack: WeakSet, needle: T, message?: string): void;*/ + + /** + * Asserts that haystack does not includes needle. + * + * T Type of haystack. + * @param haystack Object. + * @param needle Potential subset of the haystack's properties. + * @param message Message to display on error. + * / + static notInclude(haystack: T, needle: Partial, message?: string): void;*/ + + /** + * Asserts that value matches the regular expression regexp. + * + * @param value Actual value. + * @param regexp Potential match of value. + * @param message Message to display on error. + */ + static match(value: string, regexp: RegExp, message?: string): void { + // todo: not accurate impl + expect(regexp.test(value)).assertTrue() + } + + /** + * Asserts that value does not match the regular expression regexp. + * + * @param value Actual value. + * @param regexp Potential match of value. + * @param message Message to display on error. + */ + static notMatch(expected: string, regexp: RegExp, message?: string): void { + Assert.fail("Assert.notMatch unsupported") + } + + /** + * Asserts that fn will throw an error. + * + * @param fn Function that may throw. + * @param errMsgMatcher Expected error message matcher. + * @param ignored Ignored parameter. + * @param message Message to display on error. + * / + static throw(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string): void;*/ + + /** + * Asserts that the given function will throw an error. + * @param func - a function that may throw an error + */ + static throw(func: () => void): void { + Assert.throws(func) + } + + /** + * Asserts that the given function will throw an error. + * @param func - a function that may throw an error + */ + static throws(func: () => void): void { + let fnWrapper = () => { + try { + func() + } catch (e) { + throw new Error("fn thrown exception") + } + } + expect(fnWrapper).assertThrowError("fn thrown exception") + } + + /** + * Asserts that fn will throw an error. + * + * @param fn Function that may throw. + * @param errorLike Expected error constructor or error instance. + * @param errMsgMatcher Expected error message matcher. + * @param message Message to display on error. + * / + static throws( + fn: () => void, + errorLike?: ErrorConstructor | Error | null, + errMsgMatcher?: RegExp | string | null, + message?: string, + ): void;*/ + + /** + * Asserts that fn will throw an error. + * + * @param fn Function that may throw. + * @param errMsgMatcher Expected error message matcher. + * @param ignored Ignored parameter. + * @param message Message to display on error. + * / + static Throw(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string): void;*/ + + /** + * Asserts that fn will throw an error. + * + * @param fn Function that may throw. + * @param errorLike Expected error constructor or error instance. + * @param errMsgMatcher Expected error message matcher. + * @param message Message to display on error. + * / + static Throw( + fn: () => void, + errorLike?: ErrorConstructor | Error | null, + errMsgMatcher?: RegExp | string | null, + message?: string, + ): void;*/ + + /** + * Asserts that fn will not throw an error. + * + * @param fn Function that may throw. + * @param errMsgMatcher Expected error message matcher. + * @param ignored Ignored parameter. + * @param message Message to display on error. + * / + static doesNotThrow(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string): void;*/ + + /** + * Asserts that fn will not throw an error. + * + * @param fn Function that may throw. + * @param errorLike Expected error constructor or error instance. + * @param errMsgMatcher Expected error message matcher. + * @param message Message to display on error. + * / + static doesNotThrow( + fn: () => void, + errorLike?: ErrorConstructor | Error | null, + errMsgMatcher?: RegExp | string | null, + message?: string, + ): void;*/ + + /** + * Compares two values using operator. + * + * @param val1 Left value during comparison. + * @param operator Comparison operator. + * @param val2 Right value during comparison. + * @param message Message to display on error. + * / + static operator(val1: OperatorComparable, operator: Operator, val2: OperatorComparable, message?: string): void;*/ + + /** + * Asserts that the target is equal to expected, to within a +/- delta range. + * + * @param actual Actual value + * @param expected Potential expected value. + * @param delta Maximum differenced between values. + * @param message Message to display on error. + */ + static closeTo(actual: number, expected: number, delta: number, message?: string): void { + // implementation of 'assertClose' does not fit: + // expect(actual).assertClose(expected, delta) + + const diff = Math.abs(actual - expected) + if (diff == delta) + expect(diff).assertEqual(delta) + else + expect(diff).assertLess(delta) + } + + /** + * Asserts that the target is equal to expected, to within a +/- delta range. + * + * @param actual Actual value + * @param expected Potential expected value. + * @param delta Maximum differenced between values. + * @param message Message to display on error. + * / + static approximately(act: number, exp: number, delta: number, message?: string): void;*/ + + /** + * Asserts that non-object, non-array value inList appears in the flat array list. + * + * T Type of list values. + * @param inList Value expected to be in the list. + * @param list List of values. + * @param message Message to display on error. + */ + static oneOf(inList: T, list: T[], message?: string): void { + Assert.fail("Assert.oneOf unsupported") + } + + /** + * Asserts that the target does not contain any values. For arrays and + * strings, it checks the length property. For Map and Set instances, it + * checks the size property. For non-function objects, it gets the count + * of own enumerable string keys. + * + * T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + static isEmpty(object: T, message?: string): void { + // todo: implement + expect(object !== undefined).assertTrue() + } + + /** + * Asserts that the target contains values. For arrays and strings, it checks + * the length property. For Map and Set instances, it checks the size property. + * For non-function objects, it gets the count of own enumerable string keys. + * + * T Type of object. + * @param object Object to test. + * @param message Message to display on error. + */ + static isNotEmpty(object: T, message?: string): void { + // todo: implement + expect(object !== undefined).assertTrue() + } +} diff --git a/incremental/common/bridges/ohos/src/mocha/types/index.d.ts b/incremental/harness/src/ohos/index.ts similarity index 57% rename from incremental/common/bridges/ohos/src/mocha/types/index.d.ts rename to incremental/harness/src/ohos/index.ts index 61d2fc500..a7fe54fe3 100644 --- a/incremental/common/bridges/ohos/src/mocha/types/index.d.ts +++ b/incremental/harness/src/ohos/index.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,25 +13,17 @@ * limitations under the License. */ -declare global { - type Fn = () => void +export { Assert } from "./chai" +export { startTests } from "./mocha" - type SuiteFn = (title: string, fn: Fn) => void - - interface TestFn { - (title: string, fn?: Fn): void - skip(title: string, fn?: Fn): void - } - - let suite: SuiteFn - let suiteSetup: SuiteFn - let test: TestFn - - interface TimeFn { - now: () => number - } +export function test(title: any, fn?: any) { + throw new Error("unsupported test: " + title) +} - let performance: TimeFn +export function suite(title: any, fn?: any) { + throw new Error("unsupported suite: " + title) } -export {} +export function suiteSetup(title: any, fn?: any) { + throw new Error("unsupported suiteSetup: " + title) +} diff --git a/incremental/common/bridges/ohos/src/mocha/index.ts b/incremental/harness/src/ohos/mocha.ts similarity index 71% rename from incremental/common/bridges/ohos/src/mocha/index.ts rename to incremental/harness/src/ohos/mocha.ts index 2d2cf81c5..ba874380c 100644 --- a/incremental/common/bridges/ohos/src/mocha/index.ts +++ b/incremental/harness/src/ohos/mocha.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,7 @@ * limitations under the License. */ -import {describe, beforeEach, it, Size} from "@ohos/hypium" +import { describe, beforeEach, it, Size } from "@ohos/hypium" declare namespace globalThis { let __OpenHarmony: boolean @@ -22,22 +22,38 @@ declare namespace globalThis { globalThis.__OpenHarmony = true +type Fn = () => void + const suiteMap = new Map() -suite = (title: string, fn: Fn): void => { - suiteMap.set(title, fn) +export function startTests(generateGolden: boolean = false) { + globalThis.__generateGolden = generateGolden + suiteMap.forEach((fn: Fn, title: string) => { + describe(title, function () { + fn() + }) + }) } -suiteSetup = (title: string, fn: Fn): void => { +(suiteSetup as any) = (title: string, fn: Fn): void => { beforeEach(fn) } -test = ((title: string, fn?: Fn): void => { - it(fn ? title : `[SKIP] ${title}`, Size.MEDIUMTEST, fn ? fn : () => {}) -}) as TestFn +(suite as any) = (title: string, fn: Fn): void => { + suiteMap.set(title, fn) +} + +(test as any) = (title: string, fn?: Fn): void => { + it(fn ? title : `[SKIP] ${title}`, Size.MEDIUMTEST, fn ? fn : () => { }) +} -test.skip = (title: string, fn?: Fn): void => { - it(`[SKIP] ${title}`, Size.MEDIUMTEST, () => {}) +(test as any).skip = (title: string, fn?: Fn): void => { + it(`[SKIP] ${title}`, Size.MEDIUMTEST, () => { }) +} + + +interface TimeFn { + now: () => number } (performance as TimeFn) = { @@ -45,12 +61,3 @@ test.skip = (title: string, fn?: Fn): void => { return Date.now() } } - -export function startTests(generateGolden: boolean = false) { - globalThis.__generateGolden = generateGolden - suiteMap.forEach((fn: Fn, title: string) => { - describe(title, function () { - fn() - }) - }) -} diff --git a/incremental/harness/src/typescript/index.ts b/incremental/harness/src/typescript/index.ts new file mode 100644 index 000000000..a27db9dfd --- /dev/null +++ b/incremental/harness/src/typescript/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { assert as Assert } from "chai" +export { suite, suiteSetup, test } from "mocha" + +export function startTests(generateGolden: boolean = false) { + throw new Error("unsupported startTests: " + generateGolden) +} diff --git a/incremental/common/bridges/ohos/tsconfig.json b/incremental/harness/tsconfig-ohos.json similarity index 37% rename from incremental/common/bridges/ohos/tsconfig.json rename to incremental/harness/tsconfig-ohos.json index 632f9f62e..b8dd5ad6a 100644 --- a/incremental/common/bridges/ohos/tsconfig.json +++ b/incremental/harness/tsconfig-ohos.json @@ -1,13 +1,15 @@ { "extends": "@koalaui/build-common/tsconfig.json", "compilerOptions": { - "outDir": "../../build/bridges/ohos", - "rootDir": "src", "baseUrl": ".", + "rootDir": ".", + "outDir": "build", "module": "CommonJS", "paths": { - "chai": ["./src/chai"] - }, - "typeRoots": ["./src/mocha/types"] - } + "@koalaui/compat": ["../compat/ohos"], + "@koalaui/common": ["../common"], + "#harness": ["./src/ohos"] + } + }, + "include": ["./src/index.ts", "./src/ohos/**/*"] } diff --git a/incremental/harness/tsconfig.json b/incremental/harness/tsconfig.json new file mode 100644 index 000000000..1be2c0959 --- /dev/null +++ b/incremental/harness/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@koalaui/build-common/tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": ".", + "outDir": "build", + "module": "CommonJS", + "paths": { + "@koalaui/compat": ["../compat/typescript"], + "@koalaui/common": ["../common"], + "#harness": ["./src/typescript"] + } + }, + "include": ["./src/index.ts", "./src/typescript/**/*"] +} diff --git a/incremental/package.json b/incremental/package.json index f698a4de5..26620cd66 100644 --- a/incremental/package.json +++ b/incremental/package.json @@ -5,13 +5,12 @@ "build-common", "compat", "common", + "harness", "runtime", "demo-playground", "compiler-plugin" ], "devDependencies": { - "@types/chai": "4.3.10", - "chai": "4.3.10", "ts-node": "^10.7.0", "ts-patch": "^2.1.0", "tslib": "^2.3.1", @@ -21,7 +20,7 @@ "prepare": "cd node_modules/typescript && ts-patch install", "all:clean": "npm run clean --ws --if-present -s", "all:clean:unmemoized": "npm run clean:unmemoized --ws --if-present -s", - "compile": "npm run compile -w ./compat && npm run compile -w ./common && npm run compile -w ./runtime && npm run compile -w ./demo-playground && npm run compile -w ./compiler-plugin" + "compile": "npm run compile -w ./compat && npm run compile -w ./common && npm run compile -w ./harness && npm run compile -w ./runtime && npm run compile -w ./demo-playground && npm run compile -w ./compiler-plugin" }, "dependencies": { "@koalaui/fast-arktsc": "next", diff --git a/incremental/runtime/.gitlab-ci.yml b/incremental/runtime/.gitlab-ci.yml index 55db0c9b8..868a4f946 100644 --- a/incremental/runtime/.gitlab-ci.yml +++ b/incremental/runtime/.gitlab-ci.yml @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023 Huawei Device Co., Ltd. +# Copyright (c) 2022-2025 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -51,6 +51,7 @@ test runtime: - build compat - build compiler-plugin - build runtime + - build harness pack runtime: extends: diff --git a/incremental/runtime/arktsconfig-run-unmemoized.json b/incremental/runtime/arktsconfig-run-unmemoized.json index b2d38a278..05ec7d082 100644 --- a/incremental/runtime/arktsconfig-run-unmemoized.json +++ b/incremental/runtime/arktsconfig-run-unmemoized.json @@ -5,12 +5,14 @@ "baseUrl": "./build/unmemoized/src", "paths": { "@koalaui/common": ["../../../../common/src"], + "@koalaui/harness": ["../../../../harness/src/arkts"], "@koalaui/compat": ["../../../../compat/src/arkts"] } }, "include": ["build/unmemoized/src/**/*.ts", "build/unmemoized/src/*.ts"], "references": [ { "path": "../common" }, + { "path": "../harness" }, { "path": "../compat" } ] } diff --git a/incremental/runtime/arktsconfig-test-app.json b/incremental/runtime/arktsconfig-test-app.json index efd50c1c3..ea6bdd5d6 100644 --- a/incremental/runtime/arktsconfig-test-app.json +++ b/incremental/runtime/arktsconfig-test-app.json @@ -4,6 +4,7 @@ "outDir": "build/test/app", "paths": { "@koalaui/common": ["../common/src"], + "@koalaui/harness": ["../harness/src/arkts"], "@koalaui/compat": ["../compat/src/arkts"] } } diff --git a/incremental/runtime/arktsconfig-test-lib.json b/incremental/runtime/arktsconfig-test-lib.json index 6fdbd8cf1..c6063c37e 100644 --- a/incremental/runtime/arktsconfig-test-lib.json +++ b/incremental/runtime/arktsconfig-test-lib.json @@ -4,6 +4,7 @@ "outDir": "build/test/lib", "paths": { "@koalaui/common": ["../common/src"], + "@koalaui/harness": ["../harness/src/arkts"], "@koalaui/compat": ["../compat/src/arkts"] } }, diff --git a/incremental/runtime/arktsconfig-unmemoize.json b/incremental/runtime/arktsconfig-unmemoize.json index 7926a9e94..283159025 100644 --- a/incremental/runtime/arktsconfig-unmemoize.json +++ b/incremental/runtime/arktsconfig-unmemoize.json @@ -23,6 +23,7 @@ "references": [ { "path": "../compiler-plugin" }, { "path": "../common" }, + { "path": "../harness" }, { "path": "../compat" } ] } diff --git a/incremental/runtime/arktsconfig.json b/incremental/runtime/arktsconfig.json index f2abbcdad..27a33d650 100644 --- a/incremental/runtime/arktsconfig.json +++ b/incremental/runtime/arktsconfig.json @@ -5,12 +5,14 @@ "baseUrl": "./src", "paths": { "@koalaui/common": ["../../common/src"], + "@koalaui/harness": ["../../harness/src/arkts"], "@koalaui/compat": ["../../compat/src/arkts"] } }, "include": ["./src/**/*.ts"], "references": [ { "path": "../common" }, + { "path": "../harness" }, { "path": "../compat" } ] diff --git a/incremental/runtime/oh-package.json5 b/incremental/runtime/oh-package.json5 index c282b8826..cb80d4b20 100644 --- a/incremental/runtime/oh-package.json5 +++ b/incremental/runtime/oh-package.json5 @@ -12,7 +12,7 @@ "compile": "tsc -b .", "compile:unmemoize": "tsc -b tsconfig-unmemoize.json", "compile:test": "tsc -b tsconfig-test.json", - "clean": "rimraf build", + "clean": "rimraf build unmemoized", "clean:unmemoized": "rimraf unmemoized", "test": "mocha", "test:coverage": "nyc mocha", @@ -25,11 +25,10 @@ "@koalaui/compat": "1.4.1+devel" }, "devDependencies": { - "@types/chai": "^4.3.1", + "@koalaui/harness": "1.4.1+devel", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", diff --git a/incremental/runtime/package.json b/incremental/runtime/package.json index 546f115e7..1a563c850 100644 --- a/incremental/runtime/package.json +++ b/incremental/runtime/package.json @@ -44,11 +44,10 @@ "@koalaui/compat": "1.4.4+devel" }, "devDependencies": { - "@types/chai": "^4.3.1", + "@koalaui/harness": "1.4.1+devel", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", diff --git a/incremental/runtime/test-arkts/animation/Easing.test.ts b/incremental/runtime/test-arkts/animation/Easing.test.ts index b0fa5ea98..79693c925 100644 --- a/incremental/runtime/test-arkts/animation/Easing.test.ts +++ b/incremental/runtime/test-arkts/animation/Easing.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,8 +13,7 @@ * limitations under the License. */ -// TODO: the real chai exports 'assert', but 'assert' is still a keyword in ArkTS -import { Assert, suite, test } from "../harness" +import { Assert, suite, test } from "@koalaui/harness" import { float64, int32 } from "@koalaui/common" import { Easing, EasingCurve, EasingStepJump } from "../../src/animation/Easing" diff --git a/incremental/runtime/test-arkts/common/MarkableQueue.test.ts b/incremental/runtime/test-arkts/common/MarkableQueue.test.ts new file mode 100644 index 000000000..cef7b84c0 --- /dev/null +++ b/incremental/runtime/test-arkts/common/MarkableQueue.test.ts @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MarkableQueue, markableQueue } from "@koalaui/common" +import { Assert, suite, test } from "@koalaui/harness" + +const collector = new Array() + +function testQueue(queue: MarkableQueue, expected: Array) { + collector.length = 0 + queue.callCallbacks() + Assert.deepEqual(collector, expected) +} + +suite("MarkableQueue tests", () => { + + test("nothing to call without marker", () => { + const queue = markableQueue() + queue.addCallback(() => { collector.push("1a") }) + queue.addCallback(() => { collector.push("1b") }) + queue.addCallback(() => { collector.push("1c") }) + testQueue(queue, Array.of()) + }) + + test("call only marked callbacks", () => { + const queue = markableQueue() + queue.addCallback(() => { collector.push("1a") }) + queue.addCallback(() => { collector.push("1b") }) + queue.addCallback(() => { collector.push("1c") }) + queue.setMarker() + queue.addCallback(() => { collector.push("2a") }) + queue.addCallback(() => { collector.push("2b") }) + queue.addCallback(() => { collector.push("2c") }) + testQueue(queue, Array.of("1a", "1b", "1c")) + testQueue(queue, Array.of()) // called only once + }) + + test("call marked callbacks to the latest marker", () => { + const queue = markableQueue() + queue.addCallback(() => { collector.push("1a") }) + queue.addCallback(() => { collector.push("1b") }) + queue.addCallback(() => { collector.push("1c") }) + queue.setMarker() + queue.addCallback(() => { collector.push("2a") }) + queue.addCallback(() => { collector.push("2b") }) + queue.addCallback(() => { collector.push("2c") }) + queue.setMarker() + queue.addCallback(() => { collector.push("3a") }) + queue.addCallback(() => { collector.push("3b") }) + queue.addCallback(() => { collector.push("3c") }) + testQueue(queue, Array.of("1a", "1b", "1c", "2a", "2b", "2c")) + testQueue(queue, Array.of()) // called only once + }) +}) + +suite("MarkableQueue reversed tests", () => { + + test("nothing to call without marker", () => { + const queue = markableQueue(true) + queue.addCallback(() => { collector.push("1a") }) + queue.addCallback(() => { collector.push("1b") }) + queue.addCallback(() => { collector.push("1c") }) + testQueue(queue, Array.of()) + }) + + test("call only marked callbacks", () => { + const queue = markableQueue(true) + queue.addCallback(() => { collector.push("1a") }) + queue.addCallback(() => { collector.push("1b") }) + queue.addCallback(() => { collector.push("1c") }) + queue.setMarker() + queue.addCallback(() => { collector.push("2a") }) + queue.addCallback(() => { collector.push("2b") }) + queue.addCallback(() => { collector.push("2c") }) + testQueue(queue, Array.of("1c", "1b", "1a")) + testQueue(queue, Array.of()) // called only once + }) + + test("call marked callbacks from the latest marker", () => { + const queue = markableQueue(true) + queue.addCallback(() => { collector.push("1a") }) + queue.addCallback(() => { collector.push("1b") }) + queue.addCallback(() => { collector.push("1c") }) + queue.setMarker() + queue.addCallback(() => { collector.push("2a") }) + queue.addCallback(() => { collector.push("2b") }) + queue.addCallback(() => { collector.push("2c") }) + queue.setMarker() + queue.addCallback(() => { collector.push("3a") }) + queue.addCallback(() => { collector.push("3b") }) + queue.addCallback(() => { collector.push("3c") }) + testQueue(queue, Array.of("2c", "2b", "2a", "1c", "1b", "1a")) + testQueue(queue, Array.of()) // called only once + }) +}) diff --git a/incremental/runtime/test-arkts/memo/changeListener.test.ts b/incremental/runtime/test-arkts/memo/changeListener.test.ts index 8ee931071..4cf0d0328 100644 --- a/incremental/runtime/test-arkts/memo/changeListener.test.ts +++ b/incremental/runtime/test-arkts/memo/changeListener.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,8 +13,7 @@ * limitations under the License. */ -// TODO: the real chai exports 'assert', but 'assert' is still a keyword in ArkTS -import { Assert, suite, test } from "../harness" +import { Assert, suite, test } from "@koalaui/harness" import { GlobalStateManager, OnChange, diff --git a/incremental/runtime/test-arkts/memo/contextLocal.test.ts b/incremental/runtime/test-arkts/memo/contextLocal.test.ts index fa5fdbe5e..8f4e74b3f 100644 --- a/incremental/runtime/test-arkts/memo/contextLocal.test.ts +++ b/incremental/runtime/test-arkts/memo/contextLocal.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,8 +13,7 @@ * limitations under the License. */ -// TODO: the real chai exports 'assert', but 'assert' is still a keyword in ArkTS -import { Assert, suite, test } from "../harness" +import { Assert, suite, test } from "@koalaui/harness" import { asArray } from "@koalaui/common" import { State, diff --git a/incremental/runtime/test-arkts/memo/remember.test.ts b/incremental/runtime/test-arkts/memo/remember.test.ts index a21da1831..acc7a8b7b 100644 --- a/incremental/runtime/test-arkts/memo/remember.test.ts +++ b/incremental/runtime/test-arkts/memo/remember.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,8 +13,7 @@ * limitations under the License. */ -// TODO: the real chai exports 'assert', but 'assert' is still a keyword in ArkTS -import { Assert, suite, test } from "../harness" +import { Assert, suite, test } from "@koalaui/harness" import { asArray, float64, int32 } from "@koalaui/common" import { GlobalStateManager, diff --git a/incremental/runtime/test-arkts/memo/repeat.test.ts b/incremental/runtime/test-arkts/memo/repeat.test.ts index 12498c592..8e344702a 100644 --- a/incremental/runtime/test-arkts/memo/repeat.test.ts +++ b/incremental/runtime/test-arkts/memo/repeat.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,10 +13,8 @@ * limitations under the License. */ -// TODO: the real chai exports 'assert', but 'assert' is still a keyword in ArkTS -import { Assert, suite, test } from "../harness" -import { asArray, int32 } from "@koalaui/common" -import { UniqueId, KoalaCallsiteKey } from "@koalaui/common" +import { Assert, suite, test } from "@koalaui/harness" +import { asArray, int32, KoalaCallsiteKey } from "@koalaui/common" import { GlobalStateManager, Repeat, @@ -36,7 +34,11 @@ class Page { readonly id: KoalaCallsiteKey private readonly name: string constructor(name: string) { - this.id = parseInt(new UniqueId().addString(name).compute().slice(0, 10), 16) as KoalaCallsiteKey + let id: KoalaCallsiteKey = 0 + for (let i = 0; i < name.length; i++) { + id = (id << 3) | (id >> 29) ^ (name[i] as int32) + } + this.id = id this.name = name } /** @memo */ @@ -145,8 +147,8 @@ suite("repeat tests", () => { testInsert((array) => { RepeatByArray( array, - (element, _) => element.id, - (element, _) => { element.page() }) + (element: Page, _: int32) => element.id, + (element: Page, _: int32) => { element.page() }) }) }) @@ -156,8 +158,8 @@ suite("repeat tests", () => { 0, array.length as int32, (index: int32) => array[index], - (element, _) => element.id, - (element, _) => { element.page() }) + (element: Page, _: int32) => element.id, + (element: Page, _: int32) => { element.page() }) }) }) @@ -175,8 +177,8 @@ suite("repeat tests", () => { testRemove((array) => { RepeatByArray( array, - (element, _) => element.id, - (element, _) => { element.page() }) + (element: Page, _: int32) => element.id, + (element: Page, _: int32) => { element.page() }) }) }) @@ -186,8 +188,8 @@ suite("repeat tests", () => { 0, array.length as int32, (index: int32) => array[index], - (element, _) => element.id, - (element, _) => { element.page() }) + (element: Page, _: int32) => element.id, + (element: Page, _: int32) => { element.page() }) }) }) @@ -205,8 +207,8 @@ suite("repeat tests", () => { testSwap((array) => { RepeatByArray( array, - (element, _) => element.id, - (element, _) => { element.page() }) + (element: Page, _: int32) => element.id, + (element: Page, _: int32) => { element.page() }) }) }) @@ -216,8 +218,8 @@ suite("repeat tests", () => { 0, array.length as int32, (index: int32) => array[index], - (element, _) => element.id, - (element, _) => { element.page() }) + (element: Page, _: int32) => element.id, + (element: Page, _: int32) => { element.page() }) }) }) }) diff --git a/incremental/runtime/test-arkts/states/Journal.test.ts b/incremental/runtime/test-arkts/states/Journal.test.ts new file mode 100644 index 000000000..c9241669e --- /dev/null +++ b/incremental/runtime/test-arkts/states/Journal.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Assert, suite, test } from "@koalaui/harness" +import { Changes, Journal } from "../../src/states/Journal" + +function assertChange(changes: Changes | undefined, state: Object, expected: Value) { + const change = changes?.getChange(state) + Assert.isDefined(change) + Assert.equal(change?.value, expected) +} + +function assertNoChange(changes: Changes | undefined, state: Object) { + const change = changes?.getChange(state) + Assert.isUndefined(change) +} + +function assertNoChanges(journal: Journal) { + Assert.isUndefined(journal.getChanges()) +} + +suite("Journal tests", () => { + + test("new journal has no any changes", () => { + const journal = new Journal() + assertNoChanges(journal) + }) + + test("add changes to journal", () => { + const state = new Object() + const journal = new Journal() + journal.addChange(state, "value1") + journal.addChange(state, "value2") + assertChange(journal, state, "value2") + assertNoChanges(journal) + }) + + test("add marked changes to journal", () => { + const state = new Object() + const journal = new Journal() + journal.addChange(state, "value1") + journal.addChange(state, "value2") + journal.setMarker() + assertChange(journal, state, "value2") + assertChange(journal.getChanges(), state, "value2") + }) + + test("add changes to journal after marker", () => { + const state = new Object() + const journal = new Journal() + journal.addChange(state, "value1") + journal.addChange(state, "value2") + journal.setMarker() + journal.addChange(state, "value3") + journal.addChange(state, "value4") + assertChange(journal, state, "value4") + assertChange(journal.getChanges(), state, "value2") + }) + + test("remove all changes from journal", () => { + const state = new Object() + const journal = new Journal() + journal.addChange(state, "value1") + journal.addChange(state, "value2") + journal.setMarker() + journal.addChange(state, "value3") + journal.addChange(state, "value4") + journal.clear() + assertNoChange(journal, state) + assertNoChanges(journal) + }) + + test("remove marked changes from journal", () => { + const state = new Object() + const journal = new Journal() + journal.addChange(state, "value1") + journal.addChange(state, "value2") + journal.setMarker() + journal.addChange(state, "value3") + journal.addChange(state, "value4") + const changes = journal.getChanges() + Assert.isDefined(changes) + changes?.clear() + assertChange(journal, state, "value4") + assertNoChanges(journal) + }) +}) diff --git a/incremental/runtime/test-arkts/states/State.test.ts b/incremental/runtime/test-arkts/states/State.test.ts index 4925a207e..09743b713 100644 --- a/incremental/runtime/test-arkts/states/State.test.ts +++ b/incremental/runtime/test-arkts/states/State.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,8 +13,7 @@ * limitations under the License. */ -// TODO: the real chai exports 'assert', but 'assert' is still a keyword in ArkTS -import { Assert, suite, test } from "../harness" +import { Assert, suite, test } from "@koalaui/harness" import { KoalaCallsiteKey, float64, int32 } from "@koalaui/common" import { IncrementalNode, State, StateContext, TestNode, testUpdate, ValueTracker } from "../../src" import { createStateManager } from "../../src/states/State" @@ -101,11 +100,11 @@ suite("State", () => { let manager = createStateManager() const state = manager.namedState("named", (): float64 => 200) Assert.equal(state, manager.stateBy("named")!) - Assert.isDefined>(manager.stateBy("named")) + Assert.isDefined(manager.stateBy("named")) state.dispose() - Assert.isUndefined>(manager.stateBy("named")) + Assert.isUndefined(manager.stateBy("named")) state.dispose() - Assert.isUndefined>(manager.stateBy("named")) + Assert.isUndefined(manager.stateBy("named")) }) test("managed named state is not modified immediately", () => { let manager = createStateManager() @@ -399,8 +398,8 @@ suite("State", () => { computing.push("compute:inner:true") return "true" }, (old: string | undefined) => { - Assert.isUndefined>(context.stateBy("false")) - Assert.isDefined>(context.stateBy("true")) + Assert.isUndefined(context.stateBy("false")) + Assert.isDefined(context.stateBy("true")) Assert.isTrue(context.valueBy("true")) Assert.equal(old, "true") computing.push("cleanup:inner:true") @@ -413,8 +412,8 @@ suite("State", () => { computing.push("compute:inner:false") return "false" }, (old: string | undefined) => { - Assert.isUndefined>(context.stateBy("true")) - Assert.isDefined>(context.stateBy("false")) + Assert.isUndefined(context.stateBy("true")) + Assert.isDefined(context.stateBy("false")) Assert.isFalse(context.valueBy("false")) Assert.equal(old, "false") computing.push("cleanup:inner:false") @@ -735,8 +734,8 @@ suite("State", () => { const computable = manager.computableState((context: StateContext) => { const state = context.namedState("global", (): int32 => 0, true) Assert.equal(state, manager.stateBy("global")!) - Assert.isDefined>(manager.stateBy("global", true)) - Assert.isUndefined>(manager.stateBy("global", false)) + Assert.isDefined(manager.stateBy("global", true)) + Assert.isUndefined(manager.stateBy("global", false)) return state }) const globalState = computable.value @@ -756,16 +755,16 @@ suite("State", () => { const globalState = manager.namedState("global", (): float64 => Number.MAX_SAFE_INTEGER + 1) const computable = manager.computableState((context: StateContext) => { Assert.equal(globalState, manager.stateBy("global")!) - Assert.isDefined>(manager.stateBy("global", true)) - Assert.isUndefined>(manager.stateBy("global", false)) + Assert.isDefined(manager.stateBy("global", true)) + Assert.isUndefined(manager.stateBy("global", false)) const state = context.namedState("local", (): float64 => 0, false) Assert.equal(state, manager.stateBy("local")!) - Assert.isUndefined>(manager.stateBy("local", true)) - Assert.isDefined>(manager.stateBy("local", false)) + Assert.isUndefined(manager.stateBy("local", true)) + Assert.isDefined(manager.stateBy("local", false)) return state }) const localState = computable.value - Assert.isUndefined>(manager.stateBy("local")) + Assert.isUndefined(manager.stateBy("local")) Assert.equal("LocalState(local)=0", localState.toString()) localState.value = 1 Assert.equal(testUpdate(false, manager), 1) diff --git a/incremental/runtime/test-arkts/states/state_basics.test.ts b/incremental/runtime/test-arkts/states/state_basics.test.ts index 2acf313ba..9f08dd794 100644 --- a/incremental/runtime/test-arkts/states/state_basics.test.ts +++ b/incremental/runtime/test-arkts/states/state_basics.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,8 +13,7 @@ * limitations under the License. */ -// TODO: the real chai exports 'assert', but 'assert' is still a keyword in ArkTS -import { Assert, suite, test } from "../harness" +import { Assert, suite, test } from "@koalaui/harness" import { asArray, int32, uint32 } from "@koalaui/common" import { GlobalStateManager, diff --git a/incremental/runtime/test-arkts/tree/TreeNode.test.ts b/incremental/runtime/test-arkts/tree/TreeNode.test.ts index 696f2be60..9c0f78cfa 100644 --- a/incremental/runtime/test-arkts/tree/TreeNode.test.ts +++ b/incremental/runtime/test-arkts/tree/TreeNode.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,8 +13,7 @@ * limitations under the License. */ -// TODO: the real chai exports 'assert', but 'assert' is still a keyword in ArkTS -import { Assert, suite, test } from "../harness" +import { Assert, suite, test } from "@koalaui/harness" import { float64, int32, uint32 } from "@koalaui/common" import { TreeNode } from "../../src/tree/TreeNode" @@ -41,7 +40,7 @@ function assertContent(node: TreeNode, content: string) { } function assertRoot(node: TreeNode) { - Assert.isUndefined(node.parent) + Assert.isUndefined(node.parent) Assert.equal(node.depth, 0) Assert.equal(node.index, -1) } @@ -51,12 +50,12 @@ function assertLeaf(node: TreeNode) { } function assertNoChildAt(parent: TreeNode, index: int32) { - Assert.isUndefined(parent.childAt(index)) + Assert.isUndefined(parent.childAt(index)) } function assertChildAt(parent: TreeNode, index: int32): TreeNode { let child = parent.childAt(index) - Assert.isDefined(child) + Assert.isDefined(child) Assert.equal(child?.parent, parent) Assert.equal(child?.index, index) return child! @@ -237,7 +236,7 @@ suite("TreeNode", () => { " first\n" + " third") let first = root.removeChildAt(1) - Assert.isDefined(first) + Assert.isDefined(first) assertRoot(first!) assertContent(first!, "first") root.appendChild(first!) diff --git a/incremental/runtime/test-arkts/tree/TreePath.test.ts b/incremental/runtime/test-arkts/tree/TreePath.test.ts index 8e12e2a03..8a167265a 100644 --- a/incremental/runtime/test-arkts/tree/TreePath.test.ts +++ b/incremental/runtime/test-arkts/tree/TreePath.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,8 +13,7 @@ * limitations under the License. */ -// TODO: the real chai exports 'assert', but 'assert' is still a keyword in ArkTS -import { Assert, suite, test } from "../harness" +import { Assert, suite, test } from "@koalaui/harness" import { TreePath } from "../../src/tree/TreePath" suite("TreePath", () => { diff --git a/incremental/runtime/test/animation/Easing.test.ts b/incremental/runtime/test/animation/Easing.test.ts index f648ccf37..932ed6841 100644 --- a/incremental/runtime/test/animation/Easing.test.ts +++ b/incremental/runtime/test/animation/Easing.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,13 +13,13 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import { Easing, EasingCurve, EasingStepJump } from "../../src/animation/Easing" function assertEasing(easing: EasingCurve, ...expected: number[]) { const last = expected.length - 1 for (let i = 0; i <= last; i++) { - assert.equal(Math.round(100 * easing(i / last)), expected[i]) + Assert.equal(Math.round(100 * easing(i / last)), expected[i]) } } diff --git a/incremental/runtime/test/common/MarkableQueue.test.ts b/incremental/runtime/test/common/MarkableQueue.test.ts index 58f9de2b1..989ce69f2 100644 --- a/incremental/runtime/test/common/MarkableQueue.test.ts +++ b/incremental/runtime/test/common/MarkableQueue.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -14,7 +14,7 @@ */ import { MarkableQueue, markableQueue } from "@koalaui/common" -import { assert as Assert } from "chai" +import { Assert } from "@koalaui/harness" const collector = new Array() diff --git a/incremental/runtime/test/memo/changeListener.test.ts b/incremental/runtime/test/memo/changeListener.test.ts index 975be91f1..95a131762 100644 --- a/incremental/runtime/test/memo/changeListener.test.ts +++ b/incremental/runtime/test/memo/changeListener.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import { GlobalStateManager, OnChange, @@ -29,9 +29,9 @@ const collector = new Array() function testExpected(root: State, ...expected: string[]) { collector.length = 0 testTick(root, false) - assert.deepEqual(collector, []) + Assert.deepEqual(collector, []) GlobalStateManager.instance.callCallbacks() - assert.deepEqual(collector, expected) + Assert.deepEqual(collector, expected) } export function testChange( diff --git a/incremental/runtime/test/memo/contextLocal.test.ts b/incremental/runtime/test/memo/contextLocal.test.ts index 4d901e4e3..3b5875c57 100644 --- a/incremental/runtime/test/memo/contextLocal.test.ts +++ b/incremental/runtime/test/memo/contextLocal.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import { State, TestNode, @@ -28,7 +28,7 @@ const collector = new Array() function testExpected(root: State, ...expected: string[]) { collector.length = 0 testTick(root) - assert.deepEqual(collector, expected) + Assert.deepEqual(collector, expected) if (expected.length > 0) testExpected(root) } @@ -43,7 +43,7 @@ suite("contextLocal tests", () => { }) testExpected(root, "value") state.value = "second" - assert.throws(() => testTick(root)) + Assert.throws(() => testTick(root)) }) test("contextLocalScope propagates state immediately", () => { diff --git a/incremental/runtime/test/memo/remember.test.ts b/incremental/runtime/test/memo/remember.test.ts index a5c53f9d7..dc75dea4a 100644 --- a/incremental/runtime/test/memo/remember.test.ts +++ b/incremental/runtime/test/memo/remember.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import { GlobalStateManager, State, @@ -32,7 +32,7 @@ const collector = new Array() function testExpected(root: State, ...expected: string[]) { collector.length = 0 testTick(root) - assert.deepEqual(collector, expected) + Assert.deepEqual(collector, expected) if (expected.length > 0) testExpected(root) } diff --git a/incremental/runtime/test/memo/repeat.test.ts b/incremental/runtime/test/memo/repeat.test.ts index 392cd1a1e..94751ea46 100644 --- a/incremental/runtime/test/memo/repeat.test.ts +++ b/incremental/runtime/test/memo/repeat.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import { UniqueId, KoalaCallsiteKey } from "@koalaui/common" import { GlobalStateManager, @@ -54,7 +54,7 @@ function createPages(...names: string[]): ReadonlyArray { function testExpected(root: State, ...expected: string[]) { collector.length = 0 testTick(root) - assert.deepEqual(collector, expected) + Assert.deepEqual(collector, expected) if (expected.length > 0) testExpected(root) } diff --git a/incremental/runtime/test/states/Journal.test.ts b/incremental/runtime/test/states/Journal.test.ts index 712e8b550..efa85574b 100644 --- a/incremental/runtime/test/states/Journal.test.ts +++ b/incremental/runtime/test/states/Journal.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert as Assert } from "chai" +import { Assert } from "@koalaui/harness" import { Changes, Journal } from "../../src/states/Journal" function assertChange(changes: Changes | undefined, state: Object, expected: Value) { diff --git a/incremental/runtime/test/states/State.test.ts b/incremental/runtime/test/states/State.test.ts index 65c7d833b..01acd92ac 100644 --- a/incremental/runtime/test/states/State.test.ts +++ b/incremental/runtime/test/states/State.test.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import { UniqueId, KoalaCallsiteKey } from "@koalaui/common" import { IncrementalNode, MutableState, State, TestNode, testUpdate, ValueTracker } from "../../src" import { createStateManager } from "../../src/states/State" @@ -25,14 +25,14 @@ export function key(value: string): KoalaCallsiteKey { } function assertNode(state: State, presentation: string) { - assert.isFalse(state.modified) // the same node - assert.equal(state.value.toHierarchy(), presentation) + Assert.isFalse(state.modified) // the same node + Assert.equal(state.value.toHierarchy(), presentation) } function assertState(state: State, value: Value, modified = false) { - assert.equal(state.modified, modified) - assert.equal(state.value, value) - assert.equal(state.modified, modified) + Assert.equal(state.modified, modified) + Assert.equal(state.value, value) + Assert.equal(state.modified, modified) } function assertModifiedState(state: State, value: Value, modified = false) { @@ -40,8 +40,8 @@ function assertModifiedState(state: State, value: Value, modified } function assertStringsAndCleanup(array: string[], presentation: string) { - assert.isNotEmpty(array) - assert.equal(array.join(" ; "), presentation) + Assert.isNotEmpty(array) + Assert.equal(array.join(" ; "), presentation) array.splice(0, array.length) } @@ -73,15 +73,15 @@ suite("State", () => { let manager = createStateManager() let state = manager.mutableState(200) state.value = 404 - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertModifiedState(state, 404) }) test("managed state is not modified on next snapshot update", () => { let manager = createStateManager() let state = manager.mutableState(200) state.value = 404 - assert.equal(testUpdate(false, manager), 1) - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 0) assertState(state, 404) }) test("managed state is not modified if the same value is set", () => { @@ -90,43 +90,43 @@ suite("State", () => { for (let index = 0; index <= 200; index++) { state.value = index } - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(state, 200) }) test("unmanaged named state does not exist anymore", () => { let manager = createStateManager() const state = manager.namedState("named", () => 200) - assert.equal(state, manager.stateBy("named")!) - assert.isDefined>(manager.stateBy("named")!) + Assert.equal(state, manager.stateBy("named")!) + Assert.isDefined(manager.stateBy("named")) state.dispose() - assert.isUndefined(manager.stateBy("named")!) + Assert.isUndefined(manager.stateBy("named")) state.dispose() - assert.isUndefined(manager.stateBy("named")!) + Assert.isUndefined(manager.stateBy("named")) }) test("managed named state is not modified immediately", () => { let manager = createStateManager() manager.frozen = true manager.namedState("named", () => 200) manager.stateBy("named")!.value = 404 - assert.equal(manager.valueBy("named"), 200) - assert.isFalse(manager.stateBy("named")?.modified) + Assert.equal(manager.valueBy("named"), 200) + Assert.isFalse(manager.stateBy("named")?.modified) }) test("managed named state is modified on first snapshot update", () => { let manager = createStateManager() manager.namedState("named", () => 200) manager.stateBy("named")!.value = 404 - assert.equal(testUpdate(false, manager), 1) - assert.equal(manager.valueBy("named"), 404) - assert.isTrue(manager.stateBy("named")?.modified) + Assert.equal(testUpdate(false, manager), 1) + Assert.equal(manager.valueBy("named"), 404) + Assert.isTrue(manager.stateBy("named")?.modified) }) test("managed named state is not modified on next snapshot update", () => { let manager = createStateManager() manager.namedState("named", () => 200) manager.stateBy("named")!.value = 404 - assert.equal(testUpdate(false, manager), 1) - assert.equal(testUpdate(false, manager), 0) - assert.equal(manager.valueBy("named"), 404) - assert.isFalse(manager.stateBy("named")?.modified) + Assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 0) + Assert.equal(manager.valueBy("named"), 404) + Assert.isFalse(manager.stateBy("named")?.modified) }) test("managed named state is not modified if the same value is set", () => { let manager = createStateManager() @@ -134,9 +134,9 @@ suite("State", () => { for (let index = 0; index <= 200; index++) { manager.stateBy("named")!.value = index } - assert.equal(testUpdate(false, manager), 0) - assert.equal(manager.valueBy("named"), 200) - assert.isFalse(manager.stateBy("named")?.modified) + Assert.equal(testUpdate(false, manager), 0) + Assert.equal(manager.valueBy("named"), 200) + Assert.isFalse(manager.stateBy("named")?.modified) }) test("do not allow to dispose parameter state", () => { const name = "parameter" @@ -146,9 +146,9 @@ suite("State", () => { const param = scope.param(0, 200, undefined, name, true) // can be found by name if (scope.unchanged) return scope.cached const state = manager.stateBy(name)! - assert.isDefined(state) - assert.equal(state, param) - assert.throws(() => state.dispose()) + Assert.isDefined(state) + Assert.equal(state, param) + Assert.throws(() => state.dispose()) scope.recache() }).value }) @@ -160,8 +160,8 @@ suite("State", () => { const param = scope.param(0, 200, undefined, name, true) // can be found by name if (scope.unchanged) return scope.cached const state = manager.stateBy(name)! - assert.isDefined(state) - assert.equal(state, param) + Assert.isDefined(state) + Assert.equal(state, param) state.value = 404 assertModifiedState(state, 404) // check that modified state is updated @@ -175,63 +175,63 @@ suite("State", () => { const mutableState = manager.mutableState(true) const updatableNode = manager.updatableNode(new IncrementalNode(), () => mutableState.value) // updatable node needs to be recomputed after creation - assert.isTrue(updatableNode.recomputeNeeded) + Assert.isTrue(updatableNode.recomputeNeeded) updatableNode.value // updatable node is already computed after accessing - assert.isFalse(updatableNode.recomputeNeeded) - assert.equal(testUpdate(false, manager), 0) + Assert.isFalse(updatableNode.recomputeNeeded) + Assert.equal(testUpdate(false, manager), 0) // updatable node is already computed because nothing is changed - assert.isFalse(updatableNode.recomputeNeeded) + Assert.isFalse(updatableNode.recomputeNeeded) mutableState.value = !mutableState.value // updatable node does not know that the mutable state is changed - assert.isFalse(updatableNode.recomputeNeeded) - assert.equal(testUpdate(false, manager), 1) + Assert.isFalse(updatableNode.recomputeNeeded) + Assert.equal(testUpdate(false, manager), 1) // updatable node needs to be recomputed because the mutable state is changed - assert.isTrue(updatableNode.recomputeNeeded) + Assert.isTrue(updatableNode.recomputeNeeded) }) test("updatable node should not use StateManager.updateSnapshot()", () => { const manager = createStateManager() const state = manager.updatableNode(new IncrementalNode(), () => manager.updateSnapshot()) - assert.throws(() => state.value) + Assert.throws(() => state.value) }) test("updatable node should not use StateManager.updatableNode(...)", () => { const manager = createStateManager() const state = manager.updatableNode(new IncrementalNode(), () => manager.updatableNode(new IncrementalNode(), () => { })) - assert.throws(() => state.value) + Assert.throws(() => state.value) }) test("updatable node should not use StateManager.computableState(...)", () => { const manager = createStateManager() const state = manager.updatableNode(new IncrementalNode(), () => manager.updateSnapshot()) - assert.throws(() => state.value) + Assert.throws(() => state.value) }) test("computable state should be recomputable state", () => { const manager = createStateManager() const mutableState = manager.mutableState(true) const computableState = manager.computableState(() => mutableState.value) // computable state needs to be recomputed after creation - assert.isTrue(computableState.recomputeNeeded) + Assert.isTrue(computableState.recomputeNeeded) computableState.value // computable state is already computed after accessing - assert.isFalse(computableState.recomputeNeeded) - assert.equal(testUpdate(false, manager), 0) + Assert.isFalse(computableState.recomputeNeeded) + Assert.equal(testUpdate(false, manager), 0) // computable state is already computed because nothing is changed - assert.isFalse(computableState.recomputeNeeded) + Assert.isFalse(computableState.recomputeNeeded) mutableState.value = !mutableState.value // computable state does not know that the mutable state is changed - assert.isFalse(computableState.recomputeNeeded) - assert.equal(testUpdate(false, manager), 1) + Assert.isFalse(computableState.recomputeNeeded) + Assert.equal(testUpdate(false, manager), 1) // computable state needs to be recomputed because the mutable state is changed - assert.isTrue(computableState.recomputeNeeded) + Assert.isTrue(computableState.recomputeNeeded) }) test("computable state should not use StateManager.updateSnapshot()", () => { const manager = createStateManager() const state = manager.computableState(() => manager.updateSnapshot()) - assert.throws(() => state.value) + Assert.throws(() => state.value) }) test("computable state should not use StateManager.updatableNode(...)", () => { const manager = createStateManager() const state = manager.computableState(() => manager.updatableNode(new IncrementalNode(), () => { })) - assert.throws(() => state.value) + Assert.throws(() => state.value) }) test("computable state depends on managed state", () => { let manager = createStateManager() @@ -255,12 +255,12 @@ suite("State", () => { assertState(result, "<= NAME =>") assertStringsAndCleanup(computing, "main ; left ; name ; right") // computable state is not modified - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(result, "<= NAME =>") - assert.isEmpty(computing) + Assert.isEmpty(computing) // compute state only when snapshot updated name.value = "Sergey Malenkov" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertModifiedState(result, "<= Sergey Malenkov =>") assertStringsAndCleanup(computing, "main ; name") }) @@ -287,12 +287,12 @@ suite("State", () => { assertState(result, "<= NAME =>") assertStringsAndCleanup(computing, "main ; left ; name ; right") // computable state is not modified - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(result, "<= NAME =>") - assert.isEmpty(computing) + Assert.isEmpty(computing) // compute state only when snapshot updated manager.stateBy("global")!.value = "Sergey Malenkov" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertModifiedState(result, "<= Sergey Malenkov =>") assertStringsAndCleanup(computing, "main ; name") }) @@ -320,13 +320,13 @@ suite("State", () => { assertStringsAndCleanup(computing, "main ; left ; name ; right") // computable state is not modified assertState(result, "<= 1 =>") - assert.isEmpty(computing) + Assert.isEmpty(computing) // compute state only when snapshot updated - assert.equal(testUpdate(true, manager), 1) + Assert.equal(testUpdate(true, manager), 1) assertModifiedState(result, "<= 2 =>") assertStringsAndCleanup(computing, "main ; name") // compute state on next snapshot update - assert.equal(testUpdate(true, manager), 1) + Assert.equal(testUpdate(true, manager), 1) assertModifiedState(result, "<= 3 =>") assertStringsAndCleanup(computing, "main ; name") }) @@ -354,13 +354,13 @@ suite("State", () => { assertStringsAndCleanup(computing, "main ; left ; name ; right") // computable state is not modified assertState(result, "<= 1 =>") - assert.isEmpty(computing) + Assert.isEmpty(computing) // compute state only when snapshot updated - assert.equal(testUpdate(true, manager), 1) + Assert.equal(testUpdate(true, manager), 1) assertModifiedState(result, "<= 2 =>") assertStringsAndCleanup(computing, "main ; name") // compute state on next snapshot update - assert.equal(testUpdate(true, manager), 1) + Assert.equal(testUpdate(true, manager), 1) assertModifiedState(result, "<= 3 =>") assertStringsAndCleanup(computing, "main ; name") }) @@ -384,10 +384,10 @@ suite("State", () => { computing.push("compute:inner:true") return "true" }, old => { - assert.isUndefined(context.stateBy("false")!) - assert.isDefined>(context.stateBy("true")!) - assert.isTrue(context.valueBy("true")) - assert.equal(old, "true") + Assert.isUndefined(context.stateBy("false")) + Assert.isDefined(context.stateBy("true")) + Assert.isTrue(context.valueBy("true")) + Assert.equal(old, "true") computing.push("cleanup:inner:true") }) }, () => computing.push("cleanup:true")) @@ -398,10 +398,10 @@ suite("State", () => { computing.push("compute:inner:false") return "false" }, old => { - assert.isUndefined(context.stateBy("true")!) - assert.isDefined>(context.stateBy("false")!) - assert.isFalse(context.valueBy("false")) - assert.equal(old, "false") + Assert.isUndefined(context.stateBy("true")) + Assert.isDefined(context.stateBy("false")) + Assert.isFalse(context.valueBy("false")) + Assert.equal(old, "false") computing.push("cleanup:inner:false") }) }, () => computing.push("cleanup:false")) @@ -409,17 +409,17 @@ suite("State", () => { }) let condition = manager.namedState("condition", () => true) // initial computation - assert.equal(result.value, "value is true") + Assert.equal(result.value, "value is true") assertStringsAndCleanup(computing, "compute:value ; compute:condition ; compute:true ; compute:inner:true") // condition changed from true to false condition.value = false - assert.equal(testUpdate(false, manager), 1) - assert.equal(result.value, "value is false") + Assert.equal(testUpdate(false, manager), 1) + Assert.equal(result.value, "value is false") assertStringsAndCleanup(computing, "compute:condition ; compute:false ; compute:inner:false ; cleanup:true ; cleanup:inner:true") // condition changed from false to true condition.value = true - assert.equal(testUpdate(false, manager), 1) - assert.equal(result.value, "value is true") + Assert.equal(testUpdate(false, manager), 1) + Assert.equal(result.value, "value is true") assertStringsAndCleanup(computing, "compute:condition ; compute:true ; compute:inner:true ; cleanup:false ; cleanup:inner:false") }) test.skip("computable state does not support non-unique ids on add", () => { @@ -442,12 +442,12 @@ suite("State", () => { }) let condition = manager.namedState("condition", () => true) // initial computation - assert.equal(result.value, "only second") + Assert.equal(result.value, "only second") assertStringsAndCleanup(computing, "compute:condition ; compute:second") // condition changed from true to false condition.value = false - assert.equal(testUpdate(false, manager), 1) - assert.equal(result.value, "first & second") + Assert.equal(testUpdate(false, manager), 1) + Assert.equal(result.value, "first & second") assertStringsAndCleanup(computing, "compute:condition ; compute:first") }) test.skip("computable state does not support non-unique ids on remove", () => { @@ -470,41 +470,41 @@ suite("State", () => { }) let condition = manager.namedState("condition", () => false) // initial computation - assert.equal(result.value, "first & second") + Assert.equal(result.value, "first & second") assertStringsAndCleanup(computing, "compute:condition ; compute:first ; compute:second") // condition changed from false to true condition.value = true - assert.equal(testUpdate(false, manager), 1) - assert.equal(result.value, "only second") + Assert.equal(testUpdate(false, manager), 1) + Assert.equal(result.value, "only second") assertStringsAndCleanup(computing, "compute:condition") }) test("global named state must be created only once", () => { let manager = createStateManager() let created = manager.namedState("named", () => 200) let existed = manager.namedState("named", () => { - assert.fail() + Assert.fail() return 200 }) - assert.equal(created, existed) - assert.equal(created, manager.stateBy("named")) - assert.equal(existed, manager.stateBy("named")) + Assert.equal(created, existed) + Assert.equal(created, manager.stateBy("named")) + Assert.equal(existed, manager.stateBy("named")) }) test("local named state must be created only once", () => { createStateManager().computableState(context => { let created = context.namedState("named", () => 200) let existed = context.namedState("named", () => { - assert.fail() + Assert.fail() return 200 }) - assert.equal(created, existed) - assert.equal(created, context.stateBy("named")) - assert.equal(existed, context.stateBy("named")) + Assert.equal(created, existed) + Assert.equal(created, context.stateBy("named")) + Assert.equal(existed, context.stateBy("named")) }).value }) test("global named state must not be created when creating another one", () => { let manager = createStateManager() manager.namedState("named", () => { - assert.throws(() => { + Assert.throws(() => { manager.namedState("unnamed", () => 200) }) return 200 @@ -513,7 +513,7 @@ suite("State", () => { test("local named state must not be created when creating another one", () => { createStateManager().computableState(context => { context.namedState("named", () => { - assert.throws(() => { + Assert.throws(() => { context.namedState("unnamed", () => 200) }) return 200 @@ -524,7 +524,7 @@ suite("State", () => { const manager = createStateManager() const mutable = manager.mutableState("value") manager.namedState("prohibited", () => { - assert.throws(() => mutable.dispose()) + Assert.throws(() => mutable.dispose()) return mutable.value }) }) @@ -533,7 +533,7 @@ suite("State", () => { const mutable = manager.mutableState("value") const state = manager.computableState(context => { context.namedState("prohibited", () => { - assert.throws(() => mutable.dispose()) + Assert.throws(() => mutable.dispose()) return mutable.value }) }) @@ -544,8 +544,8 @@ suite("State", () => { const manager = createStateManager() const named = manager.namedState("state", () => "value") manager.namedState("prohibited", () => { - assert.equal(named, manager.stateBy("state")) - assert.throws(() => named.dispose()) + Assert.equal(named, manager.stateBy("state")) + Assert.throws(() => named.dispose()) return named.value }) }) @@ -554,8 +554,8 @@ suite("State", () => { const state = manager.computableState(context => { const named = context.namedState("state", () => "") context.namedState("prohibited", () => { - assert.equal(named, manager.stateBy("state")) - assert.throws(() => named.dispose()) + Assert.equal(named, manager.stateBy("state")) + Assert.throws(() => named.dispose()) return named.value }) }) @@ -566,7 +566,7 @@ suite("State", () => { const manager = createStateManager() const mutable = manager.mutableState("value") const state = manager.computableState(context => { - assert.throws(() => mutable.dispose()) + Assert.throws(() => mutable.dispose()) }) state.value state.dispose() @@ -575,7 +575,7 @@ suite("State", () => { const manager = createStateManager() const named = manager.namedState("state", () => "") const state = manager.computableState(context => { - assert.throws(() => named.dispose()) + Assert.throws(() => named.dispose()) }) state.value state.dispose() @@ -584,7 +584,7 @@ suite("State", () => { const manager = createStateManager() const state = manager.computableState(context => { const named = context.namedState("state", () => "") - assert.throws(() => named.dispose()) + Assert.throws(() => named.dispose()) }) state.value state.dispose() @@ -592,8 +592,8 @@ suite("State", () => { test("do not allow to update snapshot when updating computable state", () => { const manager = createStateManager() const state = manager.computableState(context => { - assert.equal(context, manager) - assert.throws(() => manager.updateSnapshot()) + Assert.equal(context, manager) + Assert.throws(() => manager.updateSnapshot()) }) state.value state.dispose() @@ -601,8 +601,8 @@ suite("State", () => { test("do not allow to create updatable node when updating computable state", () => { const manager = createStateManager() const state = manager.computableState(context => { - assert.equal(context, manager) - assert.throws(() => manager.updatableNode(new TestNode(), () => { })) + Assert.equal(context, manager) + Assert.throws(() => manager.updatableNode(new TestNode(), () => { })) }) state.value state.dispose() @@ -610,7 +610,7 @@ suite("State", () => { test("computable state must not compute something when creating local named state", () => { createStateManager().computableState(context => { context.namedState("name", () => { - assert.throws(() => { + Assert.throws(() => { context.compute(key("compute"), () => 200) }) return "NAME" @@ -629,7 +629,7 @@ suite("State", () => { assertState(computable, "NAME") // do not update result when snapshot updated mutable.value = "Sergey Malenkov" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertState(computable, "NAME") }) test("computable state from specific scope must be forgotten on dispose automatically", () => { @@ -645,18 +645,18 @@ suite("State", () => { // ensure that initial state is managed state.value = -10 assertState(initial, -1) - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertModifiedState(state, -10) assertModifiedState(initial, -10) // dispose first inner scope - assert.strictEqual(computable.value, initial) + Assert.strictEqual(computable.value, initial) selector.value = false - assert.equal(testUpdate(false, manager), 1) - assert.notStrictEqual(computable.value, initial) + Assert.equal(testUpdate(false, manager), 1) + Assert.notStrictEqual(computable.value, initial) // ensure that initial state is not managed assertState(initial, -10) state.value = -100 - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertState(initial, -10) }) test("named state from specific scope must be forgotten on dispose automatically", () => { @@ -672,13 +672,13 @@ suite("State", () => { // ensure that initial state is managed initial.value = -10 assertState(initial, -1) - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertModifiedState(initial, -10) // dispose first inner scope - assert.strictEqual(computable.value, initial) + Assert.strictEqual(computable.value, initial) selector.value = false - assert.equal(testUpdate(false, manager), 1) - assert.notStrictEqual(computable.value, initial) + Assert.equal(testUpdate(false, manager), 1) + Assert.notStrictEqual(computable.value, initial) // ensure that initial state is not managed assertState(initial, -10) initial.value = -100 @@ -697,13 +697,13 @@ suite("State", () => { // ensure that initial state is managed initial.value = -10 assertState(initial, -1) - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertModifiedState(initial, -10) // dispose first inner scope - assert.strictEqual(computable.value, initial) + Assert.strictEqual(computable.value, initial) selector.value = false - assert.equal(testUpdate(false, manager), 1) - assert.notStrictEqual(computable.value, initial) + Assert.equal(testUpdate(false, manager), 1) + Assert.notStrictEqual(computable.value, initial) // ensure that initial state is not managed assertState(initial, -10) initial.value = -100 @@ -713,129 +713,129 @@ suite("State", () => { const manager = createStateManager() const state = manager.mutableState(0) const increment = () => { - assert.throws(() => state.value++) + Assert.throws(() => state.value++) return state.value } const computable = manager.computableState(context => context.compute(key("a"), increment) + context.compute(key("b"), increment) + context.compute(key("c"), increment)) - assert.equal(computable.value, 0) - assert.equal(testUpdate(false, manager), 0) - assert.equal(computable.value, 0) + Assert.equal(computable.value, 0) + Assert.equal(testUpdate(false, manager), 0) + Assert.equal(computable.value, 0) state.value++ - assert.equal(testUpdate(false, manager), 1) - assert.equal(computable.value, 3) + Assert.equal(testUpdate(false, manager), 1) + Assert.equal(computable.value, 3) }) test("allow to compute once within a leaf scope", () => { const computable = createStateManager().computableState(context => context.compute(key("leaf"), () => context.compute(key("allowed"), () => 0, undefined, true), undefined, true)) - assert.equal(computable.value, 0) + Assert.equal(computable.value, 0) }) test("create global mutable state in local context", () => { const manager = createStateManager() const computable = manager.computableState(context => context.mutableState(0, true)) const globalState = computable.value - assert.equal("GlobalState=0", globalState.toString()) + Assert.equal("GlobalState=0", globalState.toString()) globalState.value = 1 - assert.equal(testUpdate(false, manager), 1) - assert.equal("GlobalState,modified=1", globalState.toString()) - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 1) + Assert.equal("GlobalState,modified=1", globalState.toString()) + Assert.equal(testUpdate(false, manager), 0) computable.dispose() - assert.equal("GlobalState=1", globalState.toString()) + Assert.equal("GlobalState=1", globalState.toString()) globalState.dispose() - assert.equal("GlobalState,disposed=1", globalState.toString()) + Assert.equal("GlobalState,disposed=1", globalState.toString()) }) test("create local mutable state in local context within remember", () => { const manager = createStateManager() const computable = manager.computableState(context => context.compute(0, () => context.mutableState(0, false), undefined, true)) const localState = computable.value - assert.equal("LocalState=0", localState.toString()) + Assert.equal("LocalState=0", localState.toString()) localState.value = 1 - assert.equal(testUpdate(false, manager), 1) - assert.equal("LocalState,modified=1", localState.toString()) - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 1) + Assert.equal("LocalState,modified=1", localState.toString()) + Assert.equal(testUpdate(false, manager), 0) computable.dispose() - assert.equal("LocalState,disposed=1", localState.toString()) + Assert.equal("LocalState,disposed=1", localState.toString()) localState.dispose() - assert.equal("LocalState,disposed=1", localState.toString()) + Assert.equal("LocalState,disposed=1", localState.toString()) }) test("create global named state in local context", () => { const manager = createStateManager() const computable = manager.computableState>(context => { const state = context.namedState("global", () => 0, true) - assert.equal(state, manager.stateBy("global")!) - assert.isDefined>(manager.stateBy("global", true)!) - assert.isUndefined(manager.stateBy("global", false)!) + Assert.equal(state, manager.stateBy("global")!) + Assert.isDefined(manager.stateBy("global", true)) + Assert.isUndefined(manager.stateBy("global", false)) return state }) const globalState = computable.value - assert.equal(globalState, manager.stateBy("global")) - assert.equal("GlobalState(global)=0", globalState.toString()) + Assert.equal(globalState, manager.stateBy("global")) + Assert.equal("GlobalState(global)=0", globalState.toString()) globalState.value = 1 - assert.equal(testUpdate(false, manager), 1) - assert.equal("GlobalState(global),modified=1", globalState.toString()) - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 1) + Assert.equal("GlobalState(global),modified=1", globalState.toString()) + Assert.equal(testUpdate(false, manager), 0) computable.dispose() - assert.equal("GlobalState(global)=1", globalState.toString()) + Assert.equal("GlobalState(global)=1", globalState.toString()) globalState.dispose() - assert.equal("GlobalState(global),disposed=1", globalState.toString()) + Assert.equal("GlobalState(global),disposed=1", globalState.toString()) }) test("create local named state in local context", () => { const manager = createStateManager() const globalState = manager.namedState("global", () => Number.MAX_SAFE_INTEGER + 1) const computable = manager.computableState>(context => { - assert.equal(globalState, manager.stateBy("global")!) - assert.isDefined>(manager.stateBy("global", true)!) - assert.isUndefined(manager.stateBy("global", false)!) + Assert.equal(globalState, manager.stateBy("global")!) + Assert.isDefined(manager.stateBy("global", true)) + Assert.isUndefined(manager.stateBy("global", false)) const state = context.namedState("local", () => 0, false) - assert.equal(state, manager.stateBy("local")!) - assert.isUndefined(manager.stateBy("local", true)!) - assert.isDefined>(manager.stateBy("local", false)!) + Assert.equal(state, manager.stateBy("local")!) + Assert.isUndefined(manager.stateBy("local", true)) + Assert.isDefined(manager.stateBy("local", false)) return state }) const localState = computable.value - assert.isUndefined(manager.stateBy("local")!) - assert.equal("LocalState(local)=0", localState.toString()) + Assert.isUndefined(manager.stateBy("local")) + Assert.equal("LocalState(local)=0", localState.toString()) localState.value = 1 - assert.equal(testUpdate(false, manager), 1) - assert.equal("LocalState(local),modified=1", localState.toString()) - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 1) + Assert.equal("LocalState(local),modified=1", localState.toString()) + Assert.equal(testUpdate(false, manager), 0) computable.dispose() - assert.equal("LocalState(local),disposed=1", localState.toString()) + Assert.equal("LocalState(local),disposed=1", localState.toString()) localState.dispose() - assert.equal("LocalState(local),disposed=1", localState.toString()) + Assert.equal("LocalState(local),disposed=1", localState.toString()) }) test("prohibit to create local mutable state in global context", () => { - assert.throws(() => createStateManager().mutableState(0, false)) + Assert.throws(() => createStateManager().mutableState(0, false)) }) test("prohibit to create local named state in global context", () => { - assert.throws(() => createStateManager().namedState("name", () => 0, false)) + Assert.throws(() => createStateManager().namedState("name", () => 0, false)) }) test("prohibit to create local mutable state in local context", () => { const computable = createStateManager().computableState(context => context.mutableState(0, false)) - assert.throws(() => computable.value) + Assert.throws(() => computable.value) }) test("prohibit to compute within a leaf scope", () => { const computable = createStateManager().computableState(context => context.compute(key("leaf"), () => context.compute(key("prohibit"), () => 0), undefined, true)) - assert.throws(() => computable.value) + Assert.throws(() => computable.value) }) test("prohibit to build tree within a leaf scope", () => { const computable = createStateManager().updatableNode(new TestNode(), context => context.compute(key("leaf"), () => context.attach(key("prohibit"), () => new TestNode(), () => { }), undefined, true)) - assert.throws(() => computable.value) + Assert.throws(() => computable.value) }) test("prohibit to build tree within a computable state", () => { const computable = createStateManager().computableState(context => context.attach(key("prohibit"), () => new TestNode(), () => { })) - assert.throws(() => computable.value) + Assert.throws(() => computable.value) }) test("leaf scope must not depend on managed state", () => { const manager = createStateManager() @@ -853,9 +853,9 @@ suite("State", () => { assertStringsAndCleanup(computing, "computable ; name") // compute state only when snapshot updated state.value = -1 - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertState(computable, 0) - assert.isEmpty(computing) + Assert.isEmpty(computing) }) test("0. amount of modified states including computable states that have dependants", () => { const manager = createStateManager() @@ -870,19 +870,19 @@ suite("State", () => { stateCounter++ return computable.value }) - assert.equal(testUpdate(false, manager), 0) - assert.equal(computableCounter, 0) // computable is not recomputed - assert.equal(stateCounter, 0) // state is not recomputed + Assert.equal(testUpdate(false, manager), 0) + Assert.equal(computableCounter, 0) // computable is not recomputed + Assert.equal(stateCounter, 0) // state is not recomputed assertState(state, false) - assert.equal(computableCounter, 1) // computable is recomputed - assert.equal(stateCounter, 1) // state is recomputed + Assert.equal(computableCounter, 1) // computable is recomputed + Assert.equal(stateCounter, 1) // state is recomputed mutable.value = true - assert.equal(testUpdate(false, manager), 2) - assert.equal(computableCounter, 2) // computable is recomputed automatically - assert.equal(stateCounter, 1) // state is not recomputed + Assert.equal(testUpdate(false, manager), 2) + Assert.equal(computableCounter, 2) // computable is recomputed automatically + Assert.equal(stateCounter, 1) // state is not recomputed assertModifiedState(state, true) - assert.equal(computableCounter, 2) // computable is not recomputed - assert.equal(stateCounter, 2) // state is recomputed by request + Assert.equal(computableCounter, 2) // computable is not recomputed + Assert.equal(stateCounter, 2) // state is recomputed by request }) test("1. amount of modified states including computable states that have dependants", () => { const manager = createStateManager() @@ -897,19 +897,19 @@ suite("State", () => { stateCounter++ return computable.value }) - assert.equal(testUpdate(false, manager), 0) - assert.equal(computableCounter, 0) // computable is not recomputed - assert.equal(stateCounter, 0) // state is not recomputed + Assert.equal(testUpdate(false, manager), 0) + Assert.equal(computableCounter, 0) // computable is not recomputed + Assert.equal(stateCounter, 0) // state is not recomputed assertState(state, false) - assert.equal(computableCounter, 1) // computable is recomputed - assert.equal(stateCounter, 1) // state is recomputed + Assert.equal(computableCounter, 1) // computable is recomputed + Assert.equal(stateCounter, 1) // state is recomputed mutable.value = true - assert.equal(testUpdate(false, manager), 1) - assert.equal(computableCounter, 2) // computable is recomputed automatically - assert.equal(stateCounter, 1) // state is not recomputed + Assert.equal(testUpdate(false, manager), 1) + Assert.equal(computableCounter, 2) // computable is recomputed automatically + Assert.equal(stateCounter, 1) // state is not recomputed assertState(state, false) - assert.equal(computableCounter, 2) // computable is not recomputed - assert.equal(stateCounter, 1) // state is not recomputed because computable is not modified + Assert.equal(computableCounter, 2) // computable is not recomputed + Assert.equal(stateCounter, 1) // state is not recomputed because computable is not modified }) test("2. amount of modified states including computable states that have dependants", () => { const manager = createStateManager() @@ -924,19 +924,19 @@ suite("State", () => { stateCounter++ return computable.value && false }) - assert.equal(testUpdate(false, manager), 0) - assert.equal(computableCounter, 0) // computable is not recomputed - assert.equal(stateCounter, 0) // state is not recomputed + Assert.equal(testUpdate(false, manager), 0) + Assert.equal(computableCounter, 0) // computable is not recomputed + Assert.equal(stateCounter, 0) // state is not recomputed assertState(state, false) - assert.equal(computableCounter, 1) // computable is recomputed - assert.equal(stateCounter, 1) // state is recomputed + Assert.equal(computableCounter, 1) // computable is recomputed + Assert.equal(stateCounter, 1) // state is recomputed mutable.value = true - assert.equal(testUpdate(false, manager), 2) - assert.equal(computableCounter, 2) // computable is recomputed automatically - assert.equal(stateCounter, 1) // state is not recomputed + Assert.equal(testUpdate(false, manager), 2) + Assert.equal(computableCounter, 2) // computable is recomputed automatically + Assert.equal(stateCounter, 1) // state is not recomputed assertState(state, false) - assert.equal(computableCounter, 2) // computable is not recomputed - assert.equal(stateCounter, 2) // state is recomputed by request but it is not modified + Assert.equal(computableCounter, 2) // computable is not recomputed + Assert.equal(stateCounter, 2) // state is recomputed by request but it is not modified }) test("build and update simple tree", () => { let manager = createStateManager() @@ -949,46 +949,46 @@ suite("State", () => { let secondNode: TestNode let root = manager.updatableNode(rootNode, context => { computing.push("update root") - assert.equal(context.node, rootNode) + Assert.equal(context.node, rootNode) context.compute(key("init"), () => { computing.push("init root") - assert.equal(context.node, rootNode) + Assert.equal(context.node, rootNode) rootNode.content = "root" }) if (count.value < 40) { context.attach(key("first"), () => { computing.push("create first") - assert.equal(context.node, undefined) + Assert.equal(context.node, undefined) return firstNode = new TestNode() }, () => { computing.push("update first") - assert.equal(context.node, firstNode) + Assert.equal(context.node, firstNode) context.compute(key("init"), () => { computing.push("init first") - assert.equal(context.node, firstNode) + Assert.equal(context.node, firstNode) firstNode.content = first.value }) }, () => { computing.push("detach&dispose first") - assert.equal(context.node, firstNode) + Assert.equal(context.node, firstNode) }) } if (count.value > 20) { context.attach(key("second"), () => { computing.push("create second") - assert.equal(context.node, undefined) + Assert.equal(context.node, undefined) return secondNode = new TestNode() }, () => { computing.push("update second") - assert.equal(context.node, secondNode) + Assert.equal(context.node, secondNode) context.compute(key("init"), () => { computing.push("init second") - assert.equal(context.node, secondNode) + Assert.equal(context.node, secondNode) secondNode.content = second.value }) }, () => { computing.push("detach&dispose second") - assert.equal(context.node, secondNode) + Assert.equal(context.node, secondNode) }) } }) @@ -999,16 +999,16 @@ suite("State", () => { assertStringsAndCleanup(computing, "update root ; init root ; create first ; update first ; init first ; create second ; update second ; init second") - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertNode(root, "root\n" + " first node\n" + " second node") - assert.isEmpty(computing) + Assert.isEmpty(computing) count.value = 20 - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root\n" + " first node") @@ -1016,15 +1016,15 @@ suite("State", () => { assertStringsAndCleanup(computing, "update root ; detach&dispose second") second.value = "last node" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root\n" + " first node") - assert.isEmpty(computing) + Assert.isEmpty(computing) count.value = 40 - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root\n" + " last node") @@ -1032,7 +1032,7 @@ suite("State", () => { assertStringsAndCleanup(computing, "update root ; create second ; update second ; init second ; detach&dispose first") count.value = 30 - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root\n" + " first node\n" + @@ -1041,7 +1041,7 @@ suite("State", () => { assertStringsAndCleanup(computing, "update root ; create first ; update first ; init first") count.value = 40 - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root\n" + " last node") @@ -1049,7 +1049,7 @@ suite("State", () => { assertStringsAndCleanup(computing, "update root ; detach&dispose first") second.value = "second node" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root\n" + " second node") @@ -1068,64 +1068,64 @@ suite("State", () => { let leafNode: TestNode let root = manager.updatableNode(rootNode, context => { computing.push("update root") - assert.equal(context.node, rootNode) + Assert.equal(context.node, rootNode) context.compute(key("init"), () => { computing.push("init root") - assert.equal(context.node, rootNode) + Assert.equal(context.node, rootNode) rootNode.content = "root" }) if (context.valueBy("parent").length > 0) { context.attach(key("parent"), () => { computing.push("create parent") - assert.equal(context.node, undefined) + Assert.equal(context.node, undefined) return parentNode = new TestNode() }, () => { computing.push("update parent") - assert.equal(context.node, parentNode) + Assert.equal(context.node, parentNode) context.compute(key("init"), () => { computing.push("init parent") - assert.equal(context.node, parentNode) + Assert.equal(context.node, parentNode) parentNode.content = context.valueBy("parent") }) if (context.valueBy("child").length > 0) { context.attach(key("child"), () => { computing.push("create child") - assert.equal(context.node, undefined) + Assert.equal(context.node, undefined) return childNode = new TestNode() }, () => { computing.push("update child") - assert.equal(context.node, childNode) + Assert.equal(context.node, childNode) context.compute(key("init"), () => { computing.push("init child") - assert.equal(context.node, childNode) + Assert.equal(context.node, childNode) childNode.content = context.valueBy("child") }) if (context.valueBy("leaf").length > 0) { context.attach(key("leaf"), () => { computing.push("create leaf") - assert.equal(context.node, undefined) + Assert.equal(context.node, undefined) return leafNode = new TestNode() }, () => { computing.push("update leaf") - assert.equal(context.node, leafNode) + Assert.equal(context.node, leafNode) context.compute(key("init"), () => { computing.push("init leaf") - assert.equal(context.node, leafNode) + Assert.equal(context.node, leafNode) leafNode.content = context.valueBy("leaf") }) }, () => { computing.push("detach&dispose leaf") - assert.equal(context.node, leafNode) + Assert.equal(context.node, leafNode) }) } }, () => { computing.push("detach&dispose child") - assert.equal(context.node, childNode) + Assert.equal(context.node, childNode) }) } }, () => { computing.push("detach&dispose parent") - assert.equal(context.node, parentNode) + Assert.equal(context.node, parentNode) }) } }) @@ -1137,17 +1137,17 @@ suite("State", () => { assertStringsAndCleanup(computing, "update root ; init root ; create parent ; update parent ; init parent ; create child ; update child ; init child ; create leaf ; update leaf ; init leaf") - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertNode(root, "root\n" + " parent\n" + " child\n" + " leaf") - assert.isEmpty(computing) + Assert.isEmpty(computing) manager.stateBy("leaf")!.value = "leaf node" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root\n" + " parent\n" + @@ -1157,21 +1157,21 @@ suite("State", () => { assertStringsAndCleanup(computing, "update root ; update parent ; update child ; update leaf ; init leaf") manager.stateBy("parent")!.value = "" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root") assertStringsAndCleanup(computing, "update root ; detach&dispose parent ; detach&dispose child ; detach&dispose leaf") manager.stateBy("child")!.value = "child node" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root") - assert.isEmpty(computing) + Assert.isEmpty(computing) manager.stateBy("parent")!.value = "parent node" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root\n" + " parent node\n" + @@ -1181,7 +1181,7 @@ suite("State", () => { assertStringsAndCleanup(computing, "update root ; create parent ; update parent ; init parent ; create child ; update child ; init child ; create leaf ; update leaf ; init leaf") manager.stateBy("leaf")!.value = "" - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertNode(root, "root\n" + " parent node\n" + @@ -1195,7 +1195,7 @@ suite("State", () => { manager.scheduleCallback(() => state.value = 10) assertState(state, 0) // first frame - assert.equal(testUpdate(true, manager), 1) + Assert.equal(testUpdate(true, manager), 1) assertModifiedState(state, 10) }) test("schedule state updating to the second next frame", () => { @@ -1204,10 +1204,10 @@ suite("State", () => { manager.scheduleCallback(() => manager.scheduleCallback(() => state.value = 10)) assertState(state, 0) // first frame - assert.equal(testUpdate(true, manager), 0) + Assert.equal(testUpdate(true, manager), 0) assertState(state, 0) // second frame - assert.equal(testUpdate(true, manager), 1) + Assert.equal(testUpdate(true, manager), 1) assertModifiedState(state, 10) }) test("schedule state updating to the third next frame", () => { @@ -1216,13 +1216,13 @@ suite("State", () => { manager.scheduleCallback(() => manager.scheduleCallback(() => manager.scheduleCallback(() => state.value = 10))) assertState(state, 0) // first frame - assert.equal(testUpdate(true, manager), 0) + Assert.equal(testUpdate(true, manager), 0) assertState(state, 0) // second frame - assert.equal(testUpdate(true, manager), 0) + Assert.equal(testUpdate(true, manager), 0) assertState(state, 0) // third frame - assert.equal(testUpdate(true, manager), 1) + Assert.equal(testUpdate(true, manager), 1) assertModifiedState(state, 10) }) }) @@ -1248,7 +1248,7 @@ suite("Equivalent", () => { const manager = createStateManager() const state = manager.mutableState(age16, undefined, Data.equivalent) assertState(state, age16) - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(state, age16) }) test("state is modified on first snapshot update", () => { @@ -1256,11 +1256,11 @@ suite("Equivalent", () => { const state = manager.mutableState(age16, undefined, Data.equivalent) assertState(state, age16) const age64 = new Data("age", 64) - assert.notEqual(age16, age64) + Assert.notEqual(age16, age64) state.value = age64 - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertModifiedState(state, age64) - assert.notEqual(state.value, age16) + Assert.notEqual(state.value, age16) }) test("state is not modified on next snapshot update", () => { const manager = createStateManager() @@ -1268,10 +1268,10 @@ suite("Equivalent", () => { assertState(state, age16) const age64 = new Data("age", 64) state.value = age64 - assert.equal(testUpdate(false, manager), 1) + Assert.equal(testUpdate(false, manager), 1) assertModifiedState(state, age64) - assert.notEqual(state.value, age16) - assert.equal(testUpdate(false, manager), 0) + Assert.notEqual(state.value, age16) + Assert.equal(testUpdate(false, manager), 0) assertState(state, age64) }) test("state is not modified if values are equivalent, but returns new value", () => { @@ -1279,12 +1279,12 @@ suite("Equivalent", () => { const state = manager.mutableState(age16, undefined, Data.equivalent) assertState(state, age16) const equal = new Data("age", 16) - assert.notEqual(equal, age16) - assert.isTrue(Data.equivalent(equal, age16)) + Assert.notEqual(equal, age16) + Assert.isTrue(Data.equivalent(equal, age16)) state.value = equal - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(state, equal) - assert.notEqual(state.value, age16) + Assert.notEqual(state.value, age16) }) test("frozen state is not modified if values are equivalent, but returns new value", () => { const manager = createStateManager() @@ -1292,12 +1292,12 @@ suite("Equivalent", () => { const state = manager.mutableState(age16, undefined, Data.equivalent) assertState(state, age16) const equal = new Data("age", 16) - assert.notEqual(equal, age16) - assert.isTrue(Data.equivalent(equal, age16)) + Assert.notEqual(equal, age16) + Assert.isTrue(Data.equivalent(equal, age16)) state.value = equal - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(state, equal) - assert.notEqual(state.value, age16) + Assert.notEqual(state.value, age16) }) test("state is not modified if the equivalent value is set, but returns new value", () => { const manager = createStateManager() @@ -1307,7 +1307,7 @@ suite("Equivalent", () => { for (let age = 0; age <= 16; age++) { state.value = value = new Data("age", age) } - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(state, value) }) test("frozen state is not modified if the equivalent value is set, but returns new value", () => { @@ -1319,7 +1319,7 @@ suite("Equivalent", () => { for (let age = 0; age <= 16; age++) { state.value = value = new Data("age", age) } - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(state, value) }) }) @@ -1349,8 +1349,8 @@ suite("ValueTracker", () => { throw new Error("cannot set " + value) })) assertState(state, 5) - assert.throws(() => state.value = 404) - assert.equal(testUpdate(false, manager), 0) + Assert.throws(() => state.value = 404) + Assert.equal(testUpdate(false, manager), 0) assertState(state, 5) }) test("disable state modification silently", () => { @@ -1358,7 +1358,7 @@ suite("ValueTracker", () => { const state = manager.mutableState(5, undefined, undefined, onUpdate(() => 5)) assertState(state, 5) state.value = 404 - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(state, 5) }) test("disposed state ignores tracker", () => { @@ -1366,7 +1366,7 @@ suite("ValueTracker", () => { const state = manager.mutableState(5, undefined, undefined, onUpdate(() => 5)) assertState(state, 5) state.value = 404 - assert.equal(testUpdate(false, manager), 0) + Assert.equal(testUpdate(false, manager), 0) assertState(state, 5) state.dispose() state.value = 999 diff --git a/incremental/runtime/test/states/state_basics.test.ts b/incremental/runtime/test/states/state_basics.test.ts index 3652ecaf3..b59c043cb 100644 --- a/incremental/runtime/test/states/state_basics.test.ts +++ b/incremental/runtime/test/states/state_basics.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import { uint32 } from "@koalaui/common" import { GlobalStateManager, @@ -42,17 +42,17 @@ suite("State management basics", () => { } const root = testRoot(ui) - assert.deepEqual(log, ["0 0", "1 0", "2 0"]) + Assert.deepEqual(log, ["0 0", "1 0", "2 0"]) log = [] addend.value = 10; testTick(root) - assert.deepEqual(log, []) + Assert.deepEqual(log, []) log = [] addend.value = 200; testTick(root) - assert.deepEqual(log, []) + Assert.deepEqual(log, []) }) test("Function identity is a part of positional key", () => { @@ -79,21 +79,21 @@ suite("State management basics", () => { log = [] let root = testRoot(ui) - assert.deepEqual(log, ["f1"]) + Assert.deepEqual(log, ["f1"]) log = [] testTick(root) - assert.deepEqual(log, []) + Assert.deepEqual(log, []) f.value = f2 log = [] testTick(root) - assert.deepEqual(log, ["f2"]) + Assert.deepEqual(log, ["f2"]) log = [] testTick(root) - assert.deepEqual(log, []) + Assert.deepEqual(log, []) }) test("The same test as above but without __distinct", () => { @@ -125,11 +125,11 @@ suite("State management basics", () => { log = [] const root = testRoot(ui) - assert.deepEqual(log, ["f1", "f2", "f3"]) + Assert.deepEqual(log, ["f1", "f2", "f3"]) log = [] testTick(root) - assert.deepEqual(log, []) + Assert.deepEqual(log, []) sequence.value = [f3, f2, f1] @@ -145,11 +145,11 @@ suite("State management basics", () => { // f3 the same! // f2 recomputes // f1 recomputes - assert.deepEqual(log, ["f2", "f1"]) + Assert.deepEqual(log, ["f2", "f1"]) log = [] testTick(root) - assert.deepEqual(log, []) + Assert.deepEqual(log, []) }) /* @@ -174,15 +174,15 @@ suite("State management basics", () => { const root = testRoot(ui) - assert.equal(peekNode, "head") + Assert.equal(peekNode, "head") testTick(root) - assert.equal(peekNode, "tail") + Assert.equal(peekNode, "tail") testTick(root) - assert.equal(peekNode, "head") + Assert.equal(peekNode, "head") }) test("mutable state dereference doesn't affect nested scopes", () => { @@ -206,15 +206,15 @@ suite("State management basics", () => { } const root = testRoot(ui) - assert.equal(peekNode, "head") + Assert.equal(peekNode, "head") testTick(root) - assert.equal(peekNode, "head") + Assert.equal(peekNode, "head") testTick(root) - assert.equal(peekNode, "head") + Assert.equal(peekNode, "head") }) */ @@ -244,38 +244,38 @@ suite("State management basics", () => { } const root = testRoot(ui) - assert.equal(peekY, true) - assert.equal(peekX, true) - assert.equal(peekNode, "head") + Assert.equal(peekY, true) + Assert.equal(peekX, true) + Assert.equal(peekNode, "head") y.value = !y.value testTick(root) - assert.equal(peekY, false) - assert.equal(peekX, false) - assert.equal(peekNode, "tail") + Assert.equal(peekY, false) + Assert.equal(peekX, false) + Assert.equal(peekNode, "tail") y.value = !y.value testTick(root) - assert.equal(peekY, true) - assert.equal(peekX, true) - assert.equal(peekNode, "head") + Assert.equal(peekY, true) + Assert.equal(peekX, true) + Assert.equal(peekNode, "head") }) test("Value seen immediately if not in @memo", () => { const x = mutableState(true) - assert.equal(x.value, true) + Assert.equal(x.value, true) x.value = !x.value - assert.equal(x.value, false) + Assert.equal(x.value, false) }) test("Value seen later if in @memo", () => { GlobalStateManager.instance.frozen = true const x = mutableState(true) - assert.equal(x.value, true) + Assert.equal(x.value, true) x.value = !x.value - assert.equal(x.value, true) + Assert.equal(x.value, true) GlobalStateManager.instance.frozen = false }) @@ -318,7 +318,7 @@ suite("State management basics", () => { testTick(root) testTick(root) - assert.deepEqual(result, [11, -1, 17, 41]) + Assert.deepEqual(result, [11, -1, 17, 41]) }) test("Receiver change triggers method recompute", () => { @@ -340,17 +340,17 @@ suite("State management basics", () => { } const root = testRoot(ui) - assert.deepEqual(peek, [11]) + Assert.deepEqual(peek, [11]) testTick(root) - assert.deepEqual(peek, [11]) + Assert.deepEqual(peek, [11]) x.value = new X(22) testTick(root) - assert.deepEqual(peek, [11, 22]) + Assert.deepEqual(peek, [11, 22]) testTick(root) - assert.deepEqual(peek, [11, 22]) + Assert.deepEqual(peek, [11, 22]) }) }) diff --git a/incremental/runtime/test/tree/TreeNode.test.ts b/incremental/runtime/test/tree/TreeNode.test.ts index 365db44ca..a50f84300 100644 --- a/incremental/runtime/test/tree/TreeNode.test.ts +++ b/incremental/runtime/test/tree/TreeNode.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,7 +13,7 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import { TreeNode } from "../../src/tree/TreeNode" class StringNode extends TreeNode { @@ -35,40 +35,40 @@ function contentOf(node: TreeNode) { } function assertContent(node: TreeNode, content: string) { - assert.equal(contentOf(node), content) + Assert.equal(contentOf(node), content) } function assertRoot(node: TreeNode) { - assert.isUndefined(node.parent) - assert.equal(node.depth, 0) - assert.equal(node.index, -1) + Assert.isUndefined(node.parent) + Assert.equal(node.depth, 0) + Assert.equal(node.index, -1) } function assertLeaf(node: TreeNode) { - assert.equal(node.childrenCount, 0) + Assert.equal(node.childrenCount, 0) } function assertNoChildAt(parent: TreeNode, index: number) { - assert.isUndefined(parent.childAt(index)) + Assert.isUndefined(parent.childAt(index)) } function assertChildAt(parent: TreeNode, index: number): TreeNode { let child = parent.childAt(index) - assert.isDefined(child) - assert.equal(child?.parent, parent) - assert.equal(child?.index, index) + Assert.isDefined(child) + Assert.equal(child?.parent, parent) + Assert.equal(child?.index, index) return child! } function assertToString(root: TreeNode, expected: string) { - assert.equal(root.toHierarchy(), expected) + Assert.equal(root.toHierarchy(), expected) } function assertRemoveChildrenAt(root: TreeNode, index: number, count: number, expected: number) { let childrenCount = root.childrenCount let children = root.removeChildrenAt(index, count) - assert.equal(children.length, expected) - assert.equal(root.childrenCount, childrenCount - expected) + Assert.equal(children.length, expected) + Assert.equal(root.childrenCount, childrenCount - expected) for (let i = 0; i < expected; i++) { assertRoot(children[i]) } @@ -106,7 +106,7 @@ suite("TreeNode", () => { let root = createRoot() assertContent(root, "root") assertRoot(root) - assert.equal(root.childrenCount, 3) + Assert.equal(root.childrenCount, 3) assertNoChildAt(root, Number.MIN_VALUE) assertNoChildAt(root, Number.MAX_VALUE) assertNoChildAt(root, Number.MIN_SAFE_INTEGER) @@ -125,10 +125,10 @@ suite("TreeNode", () => { assertContent(node2, "third") assertLeaf(node2) const children = root.children.slice() - assert.equal(children.length, 3) - assert.equal(children[0], node0) - assert.equal(children[1], node1) - assert.equal(children[2], node2) + Assert.equal(children.length, 3) + Assert.equal(children[0], node0) + Assert.equal(children[1], node1) + Assert.equal(children[2], node2) assertToString(root, "root\n" + " first\n" + @@ -140,70 +140,70 @@ suite("TreeNode", () => { let count = 0 let children = ["first", "second", "third"] root.forEach(node => { - assert.equal(children[count], contentOf(node)) + Assert.equal(children[count], contentOf(node)) count++ }) - assert.equal(count, 3) + Assert.equal(count, 3) }) test("iterate for each child with index", () => { let root = createRoot() let count = 0 root.forEach((node, index) => { - assert.equal(index, count) + Assert.equal(index, count) count++ }) - assert.equal(count, 3) + Assert.equal(count, 3) }) test("iterate through not every child", () => { let root = createRoot() let count = 0 - assert.isFalse(root.every(node => { + Assert.isFalse(root.every(node => { count++ return contentOf(node)?.length == 5 })) - assert.equal(count, 2) + Assert.equal(count, 2) }) test("iterate through every child with index", () => { let root = createRoot() let count = 0 let children = ["first", "second", "third"] - assert.isTrue(root.every((node, index) => { - assert.equal(index, count) + Assert.isTrue(root.every((node, index) => { + Assert.equal(index, count) count++ return children[index] === contentOf(node) })) - assert.equal(count, 3) + Assert.equal(count, 3) }) test("iterate through some children", () => { let root = createRoot() let count = 0 - assert.isTrue(root.some(node => { + Assert.isTrue(root.some(node => { count++ return contentOf(node)?.length == 6 })) - assert.equal(count, 2) + Assert.equal(count, 2) }) test("iterate through some children with index", () => { let root = createRoot() let count = 0 - assert.isTrue(root.some((node, index) => { - assert.equal(index, count) + Assert.isTrue(root.some((node, index) => { + Assert.equal(index, count) count++ return index == 0 })) - assert.equal(count, 1) + Assert.equal(count, 1) }) test("find child by content", () => { let root = createRoot() - assert.equal(root.find((node):TreeNode|undefined => contentOf(node) == "second" ? node : undefined), assertChildAt(root, 1)) + Assert.equal(root.find((node):TreeNode|undefined => contentOf(node) == "second" ? node : undefined), assertChildAt(root, 1)) }) test("find child by index", () => { let root = createRoot() - assert.equal(root.find((node, index):TreeNode|undefined => index == 1 ? node : undefined), assertChildAt(root, 1)) + Assert.equal(root.find((node, index):TreeNode|undefined => index == 1 ? node : undefined), assertChildAt(root, 1)) }) test("insert, move and remove children", () => { let root = createRoot() - assert.isFalse(root.removeChild(new StringNode("second"))) // non-existent node + Assert.isFalse(root.removeChild(new StringNode("second"))) // non-existent node assertToString(root, "root\n" + " first\n" + @@ -230,8 +230,8 @@ suite("TreeNode", () => { "root\n" + " first\n" + " third") - assert.isFalse(root.removeChild(second)) // cannot remove twice - assert.isTrue(root.insertChildAt(0, second)) + Assert.isFalse(root.removeChild(second)) // cannot remove twice + Assert.isTrue(root.insertChildAt(0, second)) assertToString(root, "root\n" + " second\n" + @@ -241,7 +241,7 @@ suite("TreeNode", () => { " first\n" + " third") let first = root.removeChildAt(1) - assert.isDefined(first) + Assert.isDefined(first) assertRoot(first!) assertContent(first!, "first") root.appendChild(first!) @@ -257,15 +257,15 @@ suite("TreeNode", () => { test("insert several children at once", () => { let root = createRoot() let children = [new StringNode("second.1"), new StringNode("second.2"), new StringNode("second.3")] - assert.isFalse(root.insertChildrenAt(Number.MIN_VALUE, ...children)) - assert.isFalse(root.insertChildrenAt(Number.MAX_VALUE, ...children)) - assert.isFalse(root.insertChildrenAt(Number.MIN_SAFE_INTEGER, ...children)) - assert.isFalse(root.insertChildrenAt(Number.MAX_SAFE_INTEGER, ...children)) - assert.isFalse(root.insertChildrenAt(-1, ...children)) - assert.isFalse(root.insertChildrenAt(4, ...children)) - assert.isFalse(root.insertChildrenAt(0.1, ...children)) - assert.isFalse(root.insertChildrenAt(2.9, ...children)) - assert.isTrue(root.insertChildrenAt(2, ...children)) + Assert.isFalse(root.insertChildrenAt(Number.MIN_VALUE, ...children)) + Assert.isFalse(root.insertChildrenAt(Number.MAX_VALUE, ...children)) + Assert.isFalse(root.insertChildrenAt(Number.MIN_SAFE_INTEGER, ...children)) + Assert.isFalse(root.insertChildrenAt(Number.MAX_SAFE_INTEGER, ...children)) + Assert.isFalse(root.insertChildrenAt(-1, ...children)) + Assert.isFalse(root.insertChildrenAt(4, ...children)) + Assert.isFalse(root.insertChildrenAt(0.1, ...children)) + Assert.isFalse(root.insertChildrenAt(2.9, ...children)) + Assert.isTrue(root.insertChildrenAt(2, ...children)) assertToString(root, "root\n" + " first\n" + @@ -274,7 +274,7 @@ suite("TreeNode", () => { " second.2\n" + " second.3\n" + " third") - assert.isTrue(root.insertChildrenAt(0, new StringNode("zero"))) + Assert.isTrue(root.insertChildrenAt(0, new StringNode("zero"))) assertToString(root, "root\n" + " zero\n" + @@ -284,7 +284,7 @@ suite("TreeNode", () => { " second.2\n" + " second.3\n" + " third") - assert.isTrue(root.insertChildrenAt(root.childrenCount, new StringNode("third.A"), new StringNode("third.B"))) + Assert.isTrue(root.insertChildrenAt(root.childrenCount, new StringNode("third.A"), new StringNode("third.B"))) assertToString(root, "root\n" + " zero\n" + @@ -353,23 +353,23 @@ suite("TreeNode", () => { }) test("remove trailing children at once", () => { let root = createDigitsRoot() - assert.equal(root.childrenCount, 10) - assert.equal(root.removeChildrenAt(Number.MIN_VALUE).length, 0) - assert.equal(root.removeChildrenAt(Number.MAX_VALUE).length, 0) - assert.equal(root.removeChildrenAt(Number.MIN_SAFE_INTEGER).length, 0) - assert.equal(root.removeChildrenAt(Number.MAX_SAFE_INTEGER).length, 0) - assert.equal(root.removeChildrenAt(-1).length, 0) - assert.equal(root.removeChildrenAt(root.childrenCount).length, 0) - assert.equal(root.removeChildrenAt(0.1).length, 0) - assert.equal(root.removeChildrenAt(2.9).length, 0) - assert.equal(root.childrenCount, 10) - assert.equal(root.removeChildrenAt(7).length, 3) - assert.equal(root.childrenCount, 7) - assert.equal(root.removeChildrenAt(6).length, 1) - assert.equal(root.childrenCount, 6) - assert.equal(root.removeChildrenAt(1).length, 5) - assert.equal(root.childrenCount, 1) - assert.equal(root.removeChildrenAt(0).length, 1) - assert.equal(root.childrenCount, 0) + Assert.equal(root.childrenCount, 10) + Assert.equal(root.removeChildrenAt(Number.MIN_VALUE).length, 0) + Assert.equal(root.removeChildrenAt(Number.MAX_VALUE).length, 0) + Assert.equal(root.removeChildrenAt(Number.MIN_SAFE_INTEGER).length, 0) + Assert.equal(root.removeChildrenAt(Number.MAX_SAFE_INTEGER).length, 0) + Assert.equal(root.removeChildrenAt(-1).length, 0) + Assert.equal(root.removeChildrenAt(root.childrenCount).length, 0) + Assert.equal(root.removeChildrenAt(0.1).length, 0) + Assert.equal(root.removeChildrenAt(2.9).length, 0) + Assert.equal(root.childrenCount, 10) + Assert.equal(root.removeChildrenAt(7).length, 3) + Assert.equal(root.childrenCount, 7) + Assert.equal(root.removeChildrenAt(6).length, 1) + Assert.equal(root.childrenCount, 6) + Assert.equal(root.removeChildrenAt(1).length, 5) + Assert.equal(root.childrenCount, 1) + Assert.equal(root.removeChildrenAt(0).length, 1) + Assert.equal(root.childrenCount, 0) }) }) diff --git a/incremental/runtime/test/tree/TreePath.test.ts b/incremental/runtime/test/tree/TreePath.test.ts index 0a3110f6d..e05424283 100644 --- a/incremental/runtime/test/tree/TreePath.test.ts +++ b/incremental/runtime/test/tree/TreePath.test.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,28 +13,28 @@ * limitations under the License. */ -import { assert } from "chai" +import { Assert } from "@koalaui/harness" import { TreePath } from "../../src/tree/TreePath" suite("TreePath", () => { let root = new TreePath("root") - test("root path is root", () => assert.strictEqual(root.root, root)) - test("root path depth", () => assert.equal(root.depth, 0)) - test("root path has undefined parent", () => assert.strictEqual(root.parent, undefined)) - test("root path to string", () => assert.equal(root.toString(), "/root")) + test("root path is root", () => Assert.strictEqual(root.root, root)) + test("root path depth", () => Assert.equal(root.depth, 0)) + test("root path has undefined parent", () => Assert.strictEqual(root.parent, undefined)) + test("root path to string", () => Assert.equal(root.toString(), "/root")) let parent = root.child("parent") let current = parent.child("node") - test("tree path has root", () => assert.strictEqual(current.root, root)) - test("tree path has parent", () => assert.strictEqual(current.parent, parent)) - test("tree path depth", () => assert.equal(current.depth, 2)) - test("tree path parent depth", () => assert.equal(current.parent?.depth, 1)) - test("tree path to string", () => assert.equal(current.toString(), "/root/parent/node")) + test("tree path has root", () => Assert.strictEqual(current.root, root)) + test("tree path has parent", () => Assert.strictEqual(current.parent, parent)) + test("tree path depth", () => Assert.equal(current.depth, 2)) + test("tree path parent depth", () => Assert.equal(current.parent?.depth, 1)) + test("tree path to string", () => Assert.equal(current.toString(), "/root/parent/node")) let sibling = parent.child("sibling") - test("siblings has the same parents", () => assert.strictEqual(current.parent, sibling.parent)) + test("siblings has the same parents", () => Assert.strictEqual(current.parent, sibling.parent)) }) diff --git a/incremental/runtime/tsconfig-run-unmemoized.json b/incremental/runtime/tsconfig-run-unmemoized.json index ce60b1b3b..6da97e358 100644 --- a/incremental/runtime/tsconfig-run-unmemoized.json +++ b/incremental/runtime/tsconfig-run-unmemoized.json @@ -10,6 +10,7 @@ "references": [ { "path": "../compiler-plugin" }, { "path": "../common" }, + { "path": "../harness" }, { "path": "../compat" } ] } diff --git a/incremental/runtime/tsconfig-test.json b/incremental/runtime/tsconfig-test.json index 8aca3becf..d1af4d46e 100644 --- a/incremental/runtime/tsconfig-test.json +++ b/incremental/runtime/tsconfig-test.json @@ -3,6 +3,7 @@ "include": ["src/**/*", "test/**/*"], "references": [ { "path": "../compiler-plugin" }, + { "path": "../harness" }, { "path": "../common" } ] } diff --git a/incremental/runtime/tsconfig-unmemoize.json b/incremental/runtime/tsconfig-unmemoize.json index 3851cd79e..8ba95f338 100644 --- a/incremental/runtime/tsconfig-unmemoize.json +++ b/incremental/runtime/tsconfig-unmemoize.json @@ -18,6 +18,7 @@ "references": [ { "path": "../compiler-plugin" }, { "path": "../common" }, + { "path": "../harness" }, { "path": "../compat" } ] } diff --git a/incremental/runtime/tsconfig.json b/incremental/runtime/tsconfig.json index 1cbc06787..8deecb6fe 100644 --- a/incremental/runtime/tsconfig.json +++ b/incremental/runtime/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@koalaui/build-common/tsconfig.json", + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. "compilerOptions": { "outDir": "build/lib", "rootDir": ".", @@ -13,6 +13,7 @@ "references": [ { "path": "../compiler-plugin" }, { "path": "../common" }, + { "path": "../harness" }, { "path": "../compat" } ] } diff --git a/incremental/test-utils/scripts/register.js b/incremental/test-utils/scripts/register.js index 63c14bc6a..ee8521aab 100644 --- a/incremental/test-utils/scripts/register.js +++ b/incremental/test-utils/scripts/register.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,7 +15,7 @@ const tsNode = require("ts-node") const path = require("path") -const { goldenSetup } = require("@koalaui/common/golden") +const { goldenSetup } = require("@koalaui/harness/golden") goldenSetup('.', '.') diff --git a/interop/oh-package.json5 b/interop/oh-package.json5 index 2db4af57c..14bc001d8 100644 --- a/interop/oh-package.json5 +++ b/interop/oh-package.json5 @@ -49,11 +49,9 @@ "@koalaui/common": "1.4.1+devel" }, "devDependencies": { - "@types/chai": "^4.3.1", "@types/mocha": "^9.1.0", "@typescript-eslint/eslint-plugin": "^5.20.0", "@typescript-eslint/parser": "^5.20.0", - "chai": "^4.3.6", "eslint": "^8.13.0", "eslint-plugin-unused-imports": "^2.0.0", "mocha": "^9.2.2", diff --git a/interop/package.json b/interop/package.json index 50327dcde..9fbe3ca29 100644 --- a/interop/package.json +++ b/interop/package.json @@ -74,4 +74,4 @@ "@koalaui/memo-tsc": "4.9.5", "@koalaui/fast-arktsc": "next" } -} \ No newline at end of file +} -- Gitee