Skip to content

Commit

Permalink
fix(setup): call stack exceeded when returning circular dependency (#380
Browse files Browse the repository at this point in the history
)

* fix(setup): issue returned from having circular dependency on a ref

* chore: refactor tests and handling of ref on unwrapping

* chore: code review
  • Loading branch information
pikax committed Jun 16, 2020
1 parent 8c449c1 commit 66f58ba
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 93 deletions.
13 changes: 9 additions & 4 deletions src/reactivity/unwrap.ts
Expand Up @@ -2,19 +2,24 @@ import { isRef } from './ref'
import { proxy, isFunction, isPlainObject, isArray } from '../utils'
import { isReactive } from './reactive'

export function unwrapRefProxy(value: any) {
export function unwrapRefProxy(value: any, map = new WeakMap()) {
if (map.has(value)) {
return map.get(value)
}

if (
isFunction(value) ||
isRef(value) ||
isArray(value) ||
isReactive(value) ||
!isPlainObject(value) ||
!Object.isExtensible(value)
!Object.isExtensible(value) ||
isRef(value)
) {
return value
}

const obj: any = {}
map.set(value, obj)

// copy symbols over
Object.getOwnPropertySymbols(value).forEach(
Expand All @@ -30,7 +35,7 @@ export function unwrapRefProxy(value: any) {

proxy(obj, k, { get, set })
} else {
obj[k] = unwrapRefProxy(r)
obj[k] = unwrapRefProxy(r, map)
}
}

Expand Down
234 changes: 145 additions & 89 deletions test/setup.spec.js
Expand Up @@ -467,105 +467,161 @@ describe('setup', () => {
.then(done)
})

it('should unwrap on the template', () => {
const vm = new Vue({
setup() {
const r = ref('r')
const nested = {
a: ref('a'),
aa: {
b: ref('aa'),
bb: {
cc: ref('aa'),
c: 'aa',
},
},
describe('setup unwrap', () => {
test('ref', () => {
const vm = new Vue({
setup() {
const r = ref('r')

const refList = ref([ref('1'), ref('2'), ref('3')])
const list = [ref('a'), ref('b')]

return {
r,
refList,
list,
}
},
template: `<div>
<p id="r">{{r}}</p>
<p id="list">{{list}}</p>
<p id="refList">{{refList}}</p>
</div>`,
}).$mount()

expect(vm.$el.querySelector('#r').textContent).toBe('r')

// shouldn't unwrap arrays
expect(
JSON.parse(vm.$el.querySelector('#list').textContent)
).toMatchObject([{ value: 'a' }, { value: 'b' }])
expect(
JSON.parse(vm.$el.querySelector('#refList').textContent)
).toMatchObject([{ value: '1' }, { value: '2' }, { value: '3' }])
})

aaa: reactive({
b: ref('aaa'),
bb: {
c: ref('aaa'),
cc: 'aaa',
test('nested', () => {
const vm = new Vue({
setup() {
const nested = {
a: ref('a'),
aa: {
b: ref('aa'),
bb: {
cc: ref('aa'),
c: 'aa',
},
},
}),

aaaa: {
b: [1],
bb: ref([1]),
bbb: reactive({
c: [1],
cc: ref([1]),

aaa: reactive({
b: ref('aaa'),
bb: {
c: ref('aaa'),
cc: 'aaa',
},
}),
bbbb: [ref(1)],
},
}

const refList = ref([ref('1'), ref('2'), ref('3')])
const list = [ref('a'), ref('b')]
aaaa: {
b: [1],
bb: ref([1]),
bbb: reactive({
c: [1],
cc: ref([1]),
}),
bbbb: [ref(1)],
},
}

return {
r,
nested,
refList,
list,
}
},
template: `<div>
<p id="r">{{r}}</p>
<p id="nested">{{nested.a}}</p>
<p id="list">{{list}}</p>
<p id="refList">{{refList}}</p>
<p id="nested_aa_b">{{ nested.aa.b }}</p>
<p id="nested_aa_bb_c">{{ nested.aa.bb.c }}</p>
<p id="nested_aa_bb_cc">{{ nested.aa.bb.cc }}</p>
<p id="nested_aaa_b">{{ nested.aaa.b }}</p>
<p id="nested_aaa_bb_c">{{ nested.aaa.bb.c }}</p>
<p id="nested_aaa_bb_cc">{{ nested.aaa.bb.cc }}</p>
<p id="nested_aaaa_b">{{ nested.aaaa.b }}</p>
<p id="nested_aaaa_bb_c">{{ nested.aaaa.bb }}</p>
<p id="nested_aaaa_bbb_cc">{{ nested.aaaa.bbb.c }}</p>
<p id="nested_aaaa_bbb_cc">{{ nested.aaaa.bbb.cc }}</p>
<p id="nested_aaaa_bbbb">{{ nested.aaaa.bbbb }}</p>
</div>`,
}).$mount()
return {
nested,
}
},
template: `<div>
<p id="nested">{{nested.a}}</p>
<p id="nested_aa_b">{{ nested.aa.b }}</p>
<p id="nested_aa_bb_c">{{ nested.aa.bb.c }}</p>
<p id="nested_aa_bb_cc">{{ nested.aa.bb.cc }}</p>
<p id="nested_aaa_b">{{ nested.aaa.b }}</p>
<p id="nested_aaa_bb_c">{{ nested.aaa.bb.c }}</p>
<p id="nested_aaa_bb_cc">{{ nested.aaa.bb.cc }}</p>
<p id="nested_aaaa_b">{{ nested.aaaa.b }}</p>
<p id="nested_aaaa_bb_c">{{ nested.aaaa.bb }}</p>
<p id="nested_aaaa_bbb_cc">{{ nested.aaaa.bbb.c }}</p>
<p id="nested_aaaa_bbb_cc">{{ nested.aaaa.bbb.cc }}</p>
<p id="nested_aaaa_bbbb">{{ nested.aaaa.bbbb }}</p>
</div>`,
}).$mount()

expect(vm.$el.querySelector('#r').textContent).toBe('r')
expect(vm.$el.querySelector('#nested').textContent).toBe('a')
expect(vm.$el.querySelector('#nested').textContent).toBe('a')

// shouldn't unwrap arrays
expect(
JSON.parse(vm.$el.querySelector('#list').textContent)
).toMatchObject([{ value: 'a' }, { value: 'b' }])
expect(
JSON.parse(vm.$el.querySelector('#refList').textContent)
).toMatchObject([{ value: '1' }, { value: '2' }, { value: '3' }])
expect(vm.$el.querySelector('#nested_aa_b').textContent).toBe('aa')
expect(vm.$el.querySelector('#nested_aa_bb_c').textContent).toBe('aa')
expect(vm.$el.querySelector('#nested_aa_bb_cc').textContent).toBe('aa')

expect(vm.$el.querySelector('#nested_aa_b').textContent).toBe('aa')
expect(vm.$el.querySelector('#nested_aa_bb_c').textContent).toBe('aa')
expect(vm.$el.querySelector('#nested_aa_bb_cc').textContent).toBe('aa')
expect(vm.$el.querySelector('#nested_aaa_b').textContent).toBe('aaa')
expect(vm.$el.querySelector('#nested_aaa_bb_c').textContent).toBe('aaa')
expect(vm.$el.querySelector('#nested_aaa_bb_cc').textContent).toBe('aaa')
})

expect(vm.$el.querySelector('#nested_aaa_b').textContent).toBe('aaa')
expect(vm.$el.querySelector('#nested_aaa_bb_c').textContent).toBe('aaa')
expect(vm.$el.querySelector('#nested_aaa_bb_cc').textContent).toBe('aaa')
it('recursive', () => {
const vm = new Vue({
setup() {
const b = {
c: 'c',
}

expect(
JSON.parse(vm.$el.querySelector('#nested_aaaa_b').textContent)
).toMatchObject([1])
expect(
JSON.parse(vm.$el.querySelector('#nested_aaaa_bb_c').textContent)
).toMatchObject([1])
expect(
JSON.parse(vm.$el.querySelector('#nested_aaaa_bbb_cc').textContent)
).toMatchObject([1])
expect(
JSON.parse(vm.$el.querySelector('#nested_aaaa_bbb_cc').textContent)
).toMatchObject([1])
expect(
JSON.parse(vm.$el.querySelector('#nested_aaaa_bbbb').textContent)
).toMatchObject([{ value: 1 }])
const recursive = {
a: {
a: 'a',
b,
},
}

b.recursive = recursive
b.r = ref('r')

return {
recursive,
}
},
template: `<div>
<p id="recursive_a">{{recursive.a.a}}</p>
<p id="recursive_b_c">{{recursive.a.b.c}}</p>
<p id="recursive_b_r">{{recursive.a.b.r}}</p>
<p id="recursive_b_recursive_a">{{recursive.a.b.recursive.a.a}}</p>
<p id="recursive_b_recursive_c">{{recursive.a.b.recursive.a.b.c}}</p>
<p id="recursive_b_recursive_r">{{recursive.a.b.recursive.a.b.r}}</p>
<p id="recursive_b_recursive_recursive_c">{{recursive.a.b.recursive.a.b.recursive.a.b.c}}</p>
<p id="recursive_b_recursive_recursive_r">{{recursive.a.b.recursive.a.b.recursive.a.b.r}}</p>
</div>`,
}).$mount()
expect(vm.$el.querySelector('#recursive_a').textContent).toBe('a')
expect(vm.$el.querySelector('#recursive_b_c').textContent).toBe('c')
expect(vm.$el.querySelector('#recursive_b_r').textContent).toBe('r')

expect(vm.$el.querySelector('#recursive_b_recursive_a').textContent).toBe(
'a'
)
expect(vm.$el.querySelector('#recursive_b_recursive_c').textContent).toBe(
'c'
)
expect(vm.$el.querySelector('#recursive_b_recursive_r').textContent).toBe(
'r'
)

expect(
vm.$el.querySelector('#recursive_b_recursive_recursive_c').textContent
).toBe('c')

expect(
vm.$el.querySelector('#recursive_b_recursive_recursive_r').textContent
).toBe('r')
})
})

it('should not unwrap built-in objects on the template', () => {
Expand Down

0 comments on commit 66f58ba

Please sign in to comment.