Skip to content

Commit 5f9cf7f

Browse files
authored
fix: Proxy model not setting defined values in parent class (#2187)
This is only an issue if we directly set a public member. In the current proxy model, we only set via `setField` type functions, so things seem to be fine. Inside of class definitions the proxy is skipped (since the proxy is just returned from the ctor). This change makes setting a member defined in the proxy class work even if you try to do it on the proxy object. ```ts class ProxyModel { myField = 4 } test = new ProxyModel(); test.myField; // 4 test.model.myField; // undefined test.myField = 5; test.myField; // 4 - WRONG test.model.myField; // 5 ```
1 parent a257296 commit 5f9cf7f

2 files changed

Lines changed: 170 additions & 1 deletion

File tree

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import dh from '@deephaven/jsapi-shim';
2+
import IrisGridModel from './IrisGridModel';
3+
import IrisGridProxyModel from './IrisGridProxyModel';
4+
import IrisGridTestUtils from './IrisGridTestUtils';
5+
6+
const irisGridTestUtils = new IrisGridTestUtils(dh);
7+
8+
type TestUnderlyingModel = IrisGridModel & {
9+
testUnderlyingMember: string;
10+
testUnderlyingFunction: () => void;
11+
get testUnderlyingGetter(): string;
12+
set testUnderlyingSetter(value: string);
13+
testMember: string;
14+
testFunction: () => void;
15+
get testGetter(): string;
16+
set testSetter(value: string);
17+
};
18+
19+
type TestProxyModel = IrisGridProxyModel & TestUnderlyingModel;
20+
21+
describe('IrisGridProxyModel', () => {
22+
let proxyModel: TestProxyModel;
23+
let underlyingModel: TestUnderlyingModel;
24+
25+
beforeEach(() => {
26+
proxyModel = irisGridTestUtils.makeModel() as TestProxyModel;
27+
underlyingModel = proxyModel.model as TestUnderlyingModel;
28+
});
29+
30+
// Getters must be set on prototype
31+
test('Proxies getting members when necessary', () => {
32+
Object.defineProperty(proxyModel, 'testMember', {
33+
value: 'proxy',
34+
writable: true,
35+
});
36+
Object.defineProperty(underlyingModel, 'testMember', {
37+
value: 'underlying',
38+
writable: true,
39+
});
40+
Object.defineProperty(underlyingModel, 'testUnderlyingMember', {
41+
value: 'underlying',
42+
writable: true,
43+
});
44+
45+
expect(proxyModel.testMember).toBe('proxy');
46+
expect(proxyModel.testUnderlyingMember).toBe('underlying');
47+
expect(underlyingModel.testMember).toBe('underlying');
48+
});
49+
50+
test('Proxies getters when necessary', () => {
51+
const testGetter = jest.fn(() => 'proxy');
52+
const testUnderlyingGetter = jest.fn(() => 'underlying');
53+
Object.defineProperty(Object.getPrototypeOf(proxyModel), 'testGetter', {
54+
get() {
55+
return testGetter();
56+
},
57+
});
58+
Object.defineProperty(
59+
Object.getPrototypeOf(underlyingModel),
60+
'testGetter',
61+
{
62+
get() {
63+
return testUnderlyingGetter();
64+
},
65+
}
66+
);
67+
Object.defineProperty(
68+
Object.getPrototypeOf(underlyingModel),
69+
'testUnderlyingGetter',
70+
{
71+
get() {
72+
return testUnderlyingGetter();
73+
},
74+
}
75+
);
76+
77+
expect(proxyModel.testGetter).toBe('proxy');
78+
expect(proxyModel.testUnderlyingGetter).toBe('underlying');
79+
expect(underlyingModel.testGetter).toBe('underlying');
80+
expect(testGetter).toHaveBeenCalledTimes(1);
81+
expect(testUnderlyingGetter).toHaveBeenCalledTimes(2);
82+
});
83+
84+
test('Proxies setting members when necessary', () => {
85+
Object.defineProperty(proxyModel, 'testMember', {
86+
value: 'proxy',
87+
writable: true,
88+
});
89+
Object.defineProperty(underlyingModel, 'testMember', {
90+
value: 'underlying',
91+
writable: true,
92+
});
93+
Object.defineProperty(underlyingModel, 'testUnderlyingMember', {
94+
value: 'underlying',
95+
writable: true,
96+
});
97+
98+
proxyModel.testMember = 'proxy2';
99+
proxyModel.testUnderlyingMember = 'underlying2';
100+
101+
expect(
102+
Object.getOwnPropertyDescriptor(proxyModel, 'testMember')?.value
103+
).toBe('proxy2');
104+
expect(underlyingModel.testUnderlyingMember).toBe('underlying2');
105+
expect(underlyingModel.testMember).toBe('underlying');
106+
});
107+
108+
// Setters must be set on prototype
109+
test('Proxies setters when necessary', () => {
110+
const testSetter = jest.fn();
111+
const testUnderlyingSetter = jest.fn();
112+
Object.defineProperty(Object.getPrototypeOf(proxyModel), 'testSetter', {
113+
set: testSetter,
114+
});
115+
Object.defineProperty(
116+
Object.getPrototypeOf(underlyingModel),
117+
'testSetter',
118+
{
119+
set: testUnderlyingSetter,
120+
}
121+
);
122+
Object.defineProperty(
123+
Object.getPrototypeOf(underlyingModel),
124+
'testUnderlyingSetter',
125+
{
126+
set: testUnderlyingSetter,
127+
}
128+
);
129+
130+
proxyModel.testSetter = 'proxy';
131+
proxyModel.testUnderlyingSetter = 'underlying';
132+
133+
expect(testSetter).toHaveBeenCalledWith('proxy');
134+
expect(testSetter).toHaveBeenCalledTimes(1);
135+
expect(testUnderlyingSetter).toHaveBeenCalledWith('underlying');
136+
expect(testUnderlyingSetter).toHaveBeenCalledTimes(1);
137+
});
138+
139+
// Functions must be set on prototype
140+
test('Proxies functions when necessary', () => {
141+
const testFn = jest.fn();
142+
const testUnderlyingFn = jest.fn();
143+
Object.defineProperty(Object.getPrototypeOf(proxyModel), 'testFunction', {
144+
value: testFn,
145+
});
146+
Object.defineProperty(
147+
Object.getPrototypeOf(underlyingModel),
148+
'testFunction',
149+
{
150+
value: testUnderlyingFn,
151+
}
152+
);
153+
Object.defineProperty(
154+
Object.getPrototypeOf(underlyingModel),
155+
'testUnderlyingFunction',
156+
{
157+
value: testUnderlyingFn,
158+
}
159+
);
160+
161+
proxyModel.testFunction();
162+
proxyModel.testUnderlyingFunction();
163+
164+
expect(testFn).toHaveBeenCalledTimes(1);
165+
expect(testUnderlyingFn).toHaveBeenCalledTimes(1);
166+
});
167+
});

packages/iris-grid/src/IrisGridProxyModel.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ class IrisGridProxyModel extends IrisGridModel implements PartitionedGridModel {
129129
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop)
130130
?.set != null;
131131

132-
if (proxyHasSetter) {
132+
const proxyHasProp = Object.prototype.hasOwnProperty.call(target, prop);
133+
134+
if (proxyHasSetter || proxyHasProp) {
133135
return Reflect.set(target, prop, value, target);
134136
}
135137

0 commit comments

Comments
 (0)