ivue 1.6.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,182 @@
1
+ import { reactive, ref } from 'vue';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { Reactive } from '../Reactive';
4
+ import { Kernel } from '../kernel';
5
+
6
+ describe('Reactive adversarial boundaries', () => {
7
+ it('richer cleanup composes as an ordinary method calling $stopEffects', () => {
8
+ const observed: number[] = [];
9
+ let closed = 0;
10
+
11
+ class $Store {
12
+ get count() {
13
+ return ref(0);
14
+ }
15
+
16
+ // the composition pattern: no reserved names, no auto-calls —
17
+ // the class's own dispose() does its work, then resets the engine
18
+ dispose() {
19
+ this.closeSocket();
20
+ (this as any).$stopEffects();
21
+ }
22
+
23
+ closeSocket() {
24
+ closed++;
25
+ }
26
+ }
27
+
28
+ const Store = Reactive($Store);
29
+ const store: any = new Store();
30
+ const originalCount = store.count;
31
+ store.$watch(
32
+ () => store.count.value,
33
+ (count: number) => observed.push(count),
34
+ { flush: 'sync' },
35
+ );
36
+
37
+ originalCount.value = 1;
38
+ expect(observed).toEqual([1]);
39
+
40
+ store.dispose();
41
+
42
+ expect(closed).toBe(1);
43
+ originalCount.value = 2;
44
+ expect(observed).toEqual([1]); // watcher dead
45
+ expect(store.count).not.toBe(originalCount); // cells reset
46
+ });
47
+
48
+ it('teardown clears only engine cache keys — consumer symbols survive', () => {
49
+ const consumerSymbol = Symbol('consumer-metadata');
50
+
51
+ class $Parent {
52
+ get inherited() {
53
+ return ref('parent');
54
+ }
55
+ }
56
+
57
+ class $Child extends $Parent {
58
+ get own() {
59
+ return ref('child');
60
+ }
61
+
62
+ grow() {
63
+ return this.own.value;
64
+ }
65
+ }
66
+
67
+ const Child = Reactive($Child);
68
+ const child: any = new Child();
69
+
70
+ // materialize cells on BOTH prototype layers + a bound method
71
+ const inheritedCell = child.inherited;
72
+ const ownCell = child.own;
73
+ const boundGrow = child.grow;
74
+ // a symbol the engine did NOT create — foreign metadata on the instance
75
+ child[consumerSymbol] = 'must survive';
76
+
77
+ child.$stopEffects();
78
+
79
+ // engine cells across the whole chain are gone: fresh identities
80
+ expect(child.inherited).not.toBe(inheritedCell);
81
+ expect(child.own).not.toBe(ownCell);
82
+ expect(child.grow).not.toBe(boundGrow);
83
+ // the consumer's symbol is untouched
84
+ expect(child[consumerSymbol]).toBe('must survive');
85
+ });
86
+
87
+ it('can allocate a fresh watcher scope after teardown', () => {
88
+ class $Store {
89
+ get count() {
90
+ return ref(0);
91
+ }
92
+ }
93
+
94
+ const Store = Reactive($Store);
95
+ const store: any = new Store();
96
+ const firstObserved = vi.fn();
97
+ store.$watch(() => store.count.value, firstObserved, { flush: 'sync' });
98
+ store.count.value = 1;
99
+ store.$stopEffects();
100
+
101
+ const secondObserved = vi.fn();
102
+ store.$watch(() => store.count.value, secondObserved, { flush: 'sync' });
103
+ store.count.value = 2;
104
+
105
+ expect(firstObserved).toHaveBeenCalledTimes(1);
106
+ expect(secondObserved).toHaveBeenCalledTimes(1);
107
+ });
108
+
109
+ it('does not poison a lazy ref cache when its factory throws once', () => {
110
+ let attempts = 0;
111
+
112
+ class $Store {
113
+ get state() {
114
+ attempts++;
115
+ if (attempts === 1) throw new Error('dependency is not ready');
116
+ return ref(7);
117
+ }
118
+ }
119
+
120
+ const Store = Reactive($Store);
121
+ const store = new Store();
122
+
123
+ expect(() => store.state).toThrow('dependency is not ready');
124
+ const recovered = store.state;
125
+ expect(recovered.value).toBe(7);
126
+ expect(store.state).toBe(recovered);
127
+ expect(attempts).toBe(2);
128
+ });
129
+
130
+ it('caches an undefined $-singleton result instead of rerunning it', () => {
131
+ const factory = vi.fn(() => undefined);
132
+
133
+ class $Store {
134
+ get $optionalService() {
135
+ return factory();
136
+ }
137
+ }
138
+
139
+ const Store = Reactive($Store);
140
+ const store = new Store();
141
+
142
+ expect(store.$optionalService).toBeUndefined();
143
+ expect(store.$optionalService).toBeUndefined();
144
+ expect(factory).toHaveBeenCalledTimes(1);
145
+ });
146
+
147
+ it('tears down the raw instance when invoked through a Vue proxy', () => {
148
+ const observed = vi.fn();
149
+
150
+ class $Store {
151
+ get count() {
152
+ return ref(0);
153
+ }
154
+ }
155
+
156
+ const Store = Reactive($Store);
157
+ const rawStore: any = new Store();
158
+ const proxiedStore: any = reactive(rawStore);
159
+ const originalCount = rawStore.count;
160
+ proxiedStore.$watch(() => rawStore.count.value, observed, { flush: 'sync' });
161
+
162
+ originalCount.value = 1;
163
+ proxiedStore.$stopEffects();
164
+ originalCount.value = 2;
165
+
166
+ expect(observed).toHaveBeenCalledTimes(1);
167
+ expect(rawStore.count).not.toBe(originalCount);
168
+ });
169
+ });
170
+
171
+ describe('Kernel adversarial values', () => {
172
+ it('preserves stored falsy values instead of taking the fallback', () => {
173
+ const kernel = new Kernel();
174
+ kernel.set('zero', 0);
175
+ kernel.set('false', false);
176
+ kernel.set('empty', '');
177
+
178
+ expect(kernel.get('zero', 1)).toBe(0);
179
+ expect(kernel.get('false', true)).toBe(false);
180
+ expect(kernel.get('empty', 'fallback')).toBe('');
181
+ });
182
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Coverage completion for the package entry and Kernel container.
3
+ */
4
+ import { describe, expect, it } from 'vitest';
5
+ import * as packageEntry from '../index';
6
+ import { Kernel, kernel } from '../kernel';
7
+
8
+ describe('package entry (index.ts)', () => {
9
+ it('re-exports the engine surface', () => {
10
+ expect(typeof packageEntry.Reactive).toBe('function');
11
+ });
12
+ });
13
+
14
+ describe('Kernel', () => {
15
+ it('sets, gets, falls back, clears', () => {
16
+ const container = new Kernel();
17
+ container.set('answer', 42);
18
+ expect(container.get('answer', 0)).toBe(42);
19
+ expect(container.get('missing', 'fallback')).toBe('fallback');
20
+ container.clear();
21
+ expect(container.get('answer', 0)).toBe(0);
22
+ expect(kernel).toBeInstanceOf(Kernel);
23
+ });
24
+ });
@@ -955,36 +955,36 @@ describe('ivue', () => {
955
955
  });
956
956
 
957
957
  it('mirrors default flags for class accessors: enumerable=false, configurable=true', () => {
958
- const inst = ivue(Flags);
959
- const d = Object.getOwnPropertyDescriptor(inst, 'hidden');
958
+ const instance = ivue(Flags);
959
+ const d = Object.getOwnPropertyDescriptor(instance, 'hidden');
960
960
 
961
961
  expect(d?.enumerable).toBe(false);
962
962
  expect(d?.configurable).toBe(true);
963
- expect(isEnumerable(inst, 'hidden')).toBe(false);
963
+ expect(isEnumerable(instance, 'hidden')).toBe(false);
964
964
  });
965
965
 
966
966
  it('preserves enumerable=true from prototype on the reactive instance', () => {
967
- const inst = ivue(Flags);
968
- const d = Object.getOwnPropertyDescriptor(inst, 'visible');
967
+ const instance = ivue(Flags);
968
+ const d = Object.getOwnPropertyDescriptor(instance, 'visible');
969
969
 
970
970
  expect(d?.enumerable).toBe(true);
971
- expect(isEnumerable(inst, 'visible')).toBe(true);
971
+ expect(isEnumerable(instance, 'visible')).toBe(true);
972
972
 
973
973
  // sanity check: hidden is not in keys; visible is
974
- const keys = Object.keys(inst);
974
+ const keys = Object.keys(instance);
975
975
  expect(keys.includes('visible')).toBe(true);
976
976
  expect(keys.includes('hidden')).toBe(false);
977
977
  });
978
978
 
979
979
  it('preserves configurable=false from prototype (cannot redefine)', () => {
980
- const inst = ivue(Flags);
981
- const d = Object.getOwnPropertyDescriptor(inst, 'locked');
980
+ const instance = ivue(Flags);
981
+ const d = Object.getOwnPropertyDescriptor(instance, 'locked');
982
982
 
983
983
  expect(d?.configurable).toBe(false);
984
984
 
985
985
  // Attempting to redefine should throw a TypeError
986
986
  expect(() =>
987
- Object.defineProperty(inst, 'locked', {
987
+ Object.defineProperty(instance, 'locked', {
988
988
  get() {
989
989
  return 99;
990
990
  },
@@ -993,14 +993,14 @@ describe('ivue', () => {
993
993
  });
994
994
 
995
995
  it('configurable=true allows redefining accessor on the reactive instance', () => {
996
- const inst = ivue(Flags);
996
+ const instance = ivue(Flags);
997
997
 
998
998
  // Before redefine
999
999
  // @ts-expect-error anyway
1000
- expect(inst.visible).toBe(2);
1000
+ expect(instance.visible).toBe(2);
1001
1001
 
1002
1002
  // Redefine should succeed (configurable=true)
1003
- Object.defineProperty(inst, 'visible', {
1003
+ Object.defineProperty(instance, 'visible', {
1004
1004
  get() {
1005
1005
  return 42;
1006
1006
  },
@@ -1009,10 +1009,10 @@ describe('ivue', () => {
1009
1009
  });
1010
1010
 
1011
1011
  // @ts-expect-error anyway
1012
- expect(inst.visible).toBe(42);
1012
+ expect(instance.visible).toBe(42);
1013
1013
 
1014
1014
  // And flags remain as set on redefinition
1015
- const d = Object.getOwnPropertyDescriptor(inst, 'visible');
1015
+ const d = Object.getOwnPropertyDescriptor(instance, 'visible');
1016
1016
  expect(d?.enumerable).toBe(true);
1017
1017
  expect(d?.configurable).toBe(true);
1018
1018
  });
@@ -1033,13 +1033,13 @@ describe('ivue', () => {
1033
1033
  x = iref(7); // normal data
1034
1034
  }
1035
1035
 
1036
- const inst = ivue(WithSymbol);
1036
+ const instance = ivue(WithSymbol);
1037
1037
 
1038
1038
  // sanity: Vue proxy still has the symbol (we won’t surface it)
1039
- expect(inst[S]).toBe(123);
1039
+ expect(instance[S]).toBe(123);
1040
1040
 
1041
1041
  // our property collector should not list symbols
1042
- const { allProps } = getClassPropertiesAccessorsMap(inst);
1042
+ const { allProps } = getClassPropertiesAccessorsMap(instance);
1043
1043
  // only string keys
1044
1044
  for (const k of allProps.keys()) {
1045
1045
  expect(typeof k).toBe('string');
@@ -1048,7 +1048,7 @@ describe('ivue', () => {
1048
1048
  expect(allProps.has('method')).toBe(false);
1049
1049
 
1050
1050
  // toRefs() should not expose symbol-backed fields
1051
- const r = inst.toRefs();
1051
+ const r = instance.toRefs();
1052
1052
  expect('x' in r).toBe(true);
1053
1053
  expect('method' in r).toBe(false);
1054
1054
  expect(Object.getOwnPropertySymbols(r).length).toBe(0);
@@ -1828,3 +1828,60 @@ describe('ivue', () => {
1828
1828
  });
1829
1829
  });
1830
1830
  });
1831
+
1832
+ describe('ivue coverage edge cases', () => {
1833
+ it('propsWithDefaults skips a required prop even when a default exists', () => {
1834
+ const typed = {
1835
+ a: { type: String, required: true },
1836
+ b: { type: Number },
1837
+ };
1838
+ const out = propsWithDefaults({ a: 'ignored', b: 2 }, { ...typed }) as Record<
1839
+ string,
1840
+ any
1841
+ >;
1842
+ // required → the `if (typed.required) continue` branch is taken
1843
+ expect('default' in out.a).toBe(false);
1844
+ expect(out.b.default).toBe(2);
1845
+ });
1846
+
1847
+ it('ivueDisableReactivity preserves BOTH getter and setter for an accessor', () => {
1848
+ class WithRW {
1849
+ static ivueDisableReactivity = new Set<keyof WithRW>(['rw']);
1850
+ _v = 1;
1851
+ get rw() {
1852
+ return this._v;
1853
+ }
1854
+ set rw(value: number) {
1855
+ this._v = value;
1856
+ }
1857
+ }
1858
+
1859
+ // ivue() path: descriptor has get AND set → both `?.bind(vue)` branches run
1860
+ const a = ivue(WithRW);
1861
+ expect(a.rw).toBe(1);
1862
+ a.rw = 5;
1863
+ expect(a.rw).toBe(5);
1864
+
1865
+ // clone() path exercises the same branch in ivueClone()
1866
+ const b = a.clone();
1867
+ expect(b.rw).toBe(5);
1868
+ b.rw = 9;
1869
+ expect(b.rw).toBe(9);
1870
+ expect(a.rw).toBe(5); // isolation
1871
+ });
1872
+
1873
+ it('deepClone tolerates a deepCloneArgs entry mapped to undefined (uses [])', () => {
1874
+ class Svc {
1875
+ value = 1;
1876
+ init(_isClone?: boolean, v = 7) {
1877
+ this.value = v;
1878
+ }
1879
+ }
1880
+ const svc = ivue(Svc);
1881
+ // has(Svc) === true but get(Svc) === undefined → the `?? []` fallback runs
1882
+ const deepCloneArgs = new Map<any, any[]>([[Svc, undefined as any]]);
1883
+ const cloned = deepClone(svc, deepCloneArgs);
1884
+ // clone() called with no args → init(true) default → value = 7
1885
+ expect(cloned.value).toBe(7);
1886
+ });
1887
+ });
package/lib/index.ts CHANGED
@@ -1,2 +1 @@
1
- export * from './ivue';
2
- // export * from './Reactive';
1
+ export * from './Reactive';
package/package.json CHANGED
@@ -1,32 +1,52 @@
1
1
  {
2
2
  "name": "ivue",
3
- "version": "1.6.1",
3
+ "version": "2.0.0",
4
4
  "description": "Infinite Vue – Class Based Architecture for Vue 3",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
8
- "require": "./dist/index.umd.js",
8
+ "types": "./dist/index.d.ts",
9
9
  "import": "./dist/index.es.js",
10
- "types": "./dist/index.d.ts"
11
- }
10
+ "require": "./dist/index.umd.js"
11
+ },
12
+ "./package.json": "./package.json"
12
13
  },
13
14
  "main": "./dist/index.umd.js",
14
15
  "module": "./dist/index.es.js",
15
16
  "types": "./dist/index.d.ts",
16
17
  "scripts": {
17
- "dev": "vite dev --host",
18
+ "dev": "vite demo --host",
19
+ "dev:demo": "vite demo --host",
20
+ "dev:flyweight": "vite examples/playground --port 5181 --host",
21
+ "build:flyweight": "vite build examples/playground",
22
+ "preview:flyweight": "vite preview examples/playground --port 5181 --host",
23
+ "measure:flyweight": "node examples/playground/src/examples/flyweight-grid/measure.mjs",
24
+ "measure:formula": "node demo/formula/measure.mjs",
25
+ "measure:grid": "node demo/grid/measure.mjs",
26
+ "dev:docs": "npm --prefix docs_v2 run dev -- --port 5174",
18
27
  "test": "vitest --coverage.enabled=true --reporter=verbose --ui",
19
28
  "coverage": "vitest run --coverage",
29
+ "bench": "vitest bench --run",
30
+ "bench:node-namespace": "vite-node experiments/node-namespace/benchmark.ts",
20
31
  "cypress": "cypress run --component",
21
32
  "cypress:headed": "cypress run --component --browser chrome --headed --no-exit",
22
33
  "build": "tsc --version;tsc --p ./tsconfig.json && vite build",
23
34
  "build:demo": "tsc --p ./tsconfig.json && vite build demo",
24
- "dev:demo": "vite demo --host",
35
+ "build:docs": "npm run sync:examples && npm --prefix docs_v2 run build",
36
+ "render:og": "node docs_v2/scripts/brand-image-generator.mjs og",
37
+ "render:form-header": "node docs_v2/scripts/brand-image-generator.mjs form-header",
25
38
  "preview:demo": "npm run build:demo && vite preview demo --host",
26
- "release": "yarn build && yarn publish"
39
+ "preview:docs": "npm --prefix docs_v2 run preview",
40
+ "release": "npm run build && npm publish",
41
+ "sync:skill": "node -e \"const fs=require('fs');fs.mkdirSync('skills/ivue',{recursive:true});fs.copyFileSync('.claude/skills/ivue/SKILL.md','skills/ivue/SKILL.md')\"",
42
+ "prepack": "npm run sync:skill",
43
+ "sync:examples": "node -e \"require('fs').copyFileSync('lib/Reactive.ts','examples/playground/src/ivue.ts')\"",
44
+ "dev:playground": "vite examples/playground --host",
45
+ "build:playground": "vite build examples/playground",
46
+ "preview:playground": "vite preview examples/playground --host"
27
47
  },
28
48
  "peerDependencies": {
29
- "vue": "^3.0.0",
49
+ "vue": "^3.2.0",
30
50
  "vue-demi": "*"
31
51
  },
32
52
  "peerDependenciesMeta": {
@@ -37,6 +57,7 @@
37
57
  "devDependencies": {
38
58
  "@cypress/vite-dev-server": "^5.0.2",
39
59
  "@cypress/vue": "^5.0.3",
60
+ "@quasar/extras": "^2.0.2",
40
61
  "@types/lodash": "^4.17.20",
41
62
  "@typescript-eslint/eslint-plugin": "^5.10.0",
42
63
  "@typescript-eslint/parser": "^5.10.0",
@@ -55,9 +76,12 @@
55
76
  "eslint-plugin-import": "^2.29.1",
56
77
  "eslint-plugin-storybook": "^0.6.12",
57
78
  "eslint-plugin-vue": "^9.14.1",
79
+ "fast-formula-parser": "^1.0.19",
58
80
  "i": "^0.3.7",
59
81
  "jsdom": "^21.0.0",
60
82
  "npm": "^11.6.2",
83
+ "playwright": "^1.61.1",
84
+ "quasar": "^2.21.3",
61
85
  "rollup-plugin-terser": "^7.0.2",
62
86
  "typescript": "^5.9.3",
63
87
  "vite": "^4.0.4",
@@ -97,7 +121,12 @@
97
121
  },
98
122
  "files": [
99
123
  "lib",
100
- "dist"
124
+ "dist",
125
+ "bin",
126
+ "skills"
101
127
  ],
102
- "license": "MIT"
128
+ "license": "MIT",
129
+ "bin": {
130
+ "ivue": "bin/ivue.mjs"
131
+ }
103
132
  }