Skip to content

Commit 8cb66f0

Browse files
committed
Add unit tests for markup control client-side array hanling
Including a small fix for return values of the wrapped observableArray functions, making it possible to use indexOf, splice, etc
1 parent 1ebafff commit 8cb66f0

File tree

2 files changed

+89
-3
lines changed

2 files changed

+89
-3
lines changed

src/Framework/Framework/Resources/Scripts/tests/markupControls.test.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isObservableArray } from '../utils/knockout'
12
import { initDotvvm } from './helper'
23

34
const viewModel = {
@@ -6,7 +7,8 @@ const viewModel = {
67
ComplexObj: {
78
A: 1,
89
B: [ 1, 2, 3 ]
9-
}
10+
},
11+
NullField: null
1012
}
1113
initDotvvm({viewModel})
1214

@@ -131,3 +133,87 @@ it.each([
131133
if (requiresSync) dotvvm.rootStateManager.doUpdateNow()
132134
expect(context.Obj.state).toStrictEqual({ Something: {...viewModel.ComplexObj, A: 43210}, Arr: [1, 2] })
133135
})
136+
137+
it.each([
138+
[ "[1,2]", true, false ],
139+
[ "ko.observableArray([1, 2])", true, true ],
140+
[ "ComplexObj().B", true, true ],
141+
[ "ComplexObj().B()", true, false ],
142+
[ "ComplexObj().B().map(_ => 1)", true, false ],
143+
[ "ComplexObj().B().length", false, false ],
144+
[ "ComplexObj", false, false ],
145+
[ "ComplexObj()", false, false ],
146+
[ "null", false, false ],
147+
[ "NullField", false, false ],
148+
])("dotvvm-with-control-properties makes observable array before its touched (%s)", (binding, isArray, tryObservableArray) => {
149+
dotvvm.setState(viewModel); dotvvm.rootStateManager.doUpdateNow()
150+
151+
const div = document.createElement("div")
152+
div.innerHTML = `
153+
<div data-bind="dotvvm-with-control-properties: {
154+
Arr: ${binding}
155+
}">
156+
<span id=x />
157+
<div data-bind="dotvvm-with-control-properties: { Arr: $control.Arr }">
158+
<span id=y />
159+
</div>
160+
</div>
161+
`
162+
163+
ko.applyBindings(dotvvm.viewModelObservables.root, div)
164+
165+
const x = div.querySelector("#x")!
166+
const y = div.querySelector("#y")!
167+
const contextX: any = ko.contextFor(x).$control
168+
const contextY: any = ko.contextFor(y).$control
169+
170+
expect(contextX.Arr).observable()
171+
expect(contextY.Arr).observable()
172+
173+
expect(isObservableArray(contextX.Arr)).toBe(isArray)
174+
expect(isObservableArray(contextY.Arr)).toBe(isArray)
175+
176+
if (tryObservableArray) {
177+
contextX.Arr.unshift(99)
178+
const ix = contextX.Arr.indexOf(contextX.Arr()[0])
179+
const ix2 = contextY.Arr.indexOf(contextX.Arr()[0])
180+
expect(ix2).toEqual(ix)
181+
}
182+
})
183+
184+
test("dotvvm-with-control-properties correctly wraps null changed to array", () => {
185+
dotvvm.setState(viewModel)
186+
187+
dotvvm.patchState({ ComplexObj: null })
188+
dotvvm.rootStateManager.doUpdateNow()
189+
190+
const div = document.createElement("div")
191+
div.innerHTML = `
192+
<div data-bind="dotvvm-with-control-properties: {
193+
Arr: ComplexObj()?.B
194+
}"> <span id=x /> </div>
195+
`
196+
ko.applyBindings(dotvvm.viewModelObservables.root, div)
197+
const x = div.querySelector("#x")!
198+
const context: any = ko.contextFor(x).$control
199+
expect(context.Arr).observable()
200+
expect(context.Arr.state).toEqual(undefined)
201+
expect(context.Arr()).toEqual(undefined)
202+
expect("push" in context.Arr).toBe(false)
203+
204+
dotvvm.patchState({ ComplexObj: { B: [1, 2] } })
205+
dotvvm.rootStateManager.doUpdateNow()
206+
207+
expect(context.Arr()?.map((x: any) => x())).toStrictEqual([1, 2])
208+
expect(context.Arr.state).toStrictEqual([1, 2])
209+
expect("push" in context.Arr).toBe(true)
210+
211+
context.Arr.push(3)
212+
expect(context.Arr()?.map((x:any) => x())).toStrictEqual([1, 2, 3])
213+
expect(dotvvm.state.ComplexObj.B).toStrictEqual([1, 2, 3])
214+
215+
context.Arr.unshift(0)
216+
context.Arr.splice(1, 1)
217+
218+
expect(dotvvm.state.ComplexObj.B).toStrictEqual([0, 2, 3])
219+
})

src/Framework/Framework/Resources/Scripts/utils/evaluator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ export function proxyObservableArrayMethods(wrapper: KnockoutObservable<any>, ge
116116
const result = getExpressionResult(getObservableArray)
117117

118118
if (!isObservableArray(result)) {
119-
logError("validation", `Cannot execute '${fnName}' function on ko.computed because the expression '${getObservableArray}' does not return an observable array.`)
119+
return logError("validation", compileConstants.debug ? `Cannot execute '${fnName}' function on ko.computed because the expression '${getObservableArray}' does not return an observable array.` : 'Target is not observableArray')
120120
} else {
121-
result[fnName].apply(result, args)
121+
return result[fnName](...args)
122122
}
123123
}
124124
}

0 commit comments

Comments
 (0)