Skip to content

Commit

Permalink
ElemType: preventing a double call to the "mount" lifecycle method (b…
Browse files Browse the repository at this point in the history
…ug fixed)
  • Loading branch information
aristov committed Mar 28, 2024
1 parent ca17c3c commit fa0e198
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 88 deletions.
20 changes: 17 additions & 3 deletions lib/ElemType.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const descriptors = {
__ref : nullDescriptor,
__handlers : nullDescriptor,
__sampleNode : nullDescriptor,
__mounted : {
writable : true,
value : false,
},
vnode : nullDescriptor,
node : nullDescriptor,
children : nullDescriptor,
Expand Down Expand Up @@ -421,11 +425,15 @@ export class ElemType
* @private
*/
__mount() {
if(this.__mounted) {
return
}
let child
for(child of this.children) {
child.props && child.__mount()
}
this.mount()
this.__mounted = true
}

/**
Expand Down Expand Up @@ -571,7 +579,9 @@ export class ElemType
else {
node.append(childB.__node || childB.node)
}
childB.__mount()
if(this.__mounted) {
childB.__mount()
}
}
for(i = 0; i < length; i++) {
childB = childrenB[i]
Expand Down Expand Up @@ -619,7 +629,9 @@ export class ElemType
childB.__parent = this
childB.__init()
this.node.append(childB.__node || childB.node)
childB.__mount?.()
if(this.__mounted) {
childB.__mount?.()
}
continue
}
if(childB === undefined) {
Expand Down Expand Up @@ -651,7 +663,9 @@ export class ElemType
childB.__parent = this
childB.__init()
nodeA.replaceWith(childB.__node || childB.node)
childB.__mount?.()
if(this.__mounted) {
childB.__mount?.()
}
}
}

Expand Down
97 changes: 97 additions & 0 deletions test/mount-1.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import test from 'ava'
import sinon from 'sinon'
import { ElemType } from '../index.js'

let mountA, mountB

class ChildA extends ElemType
{
static class = 'ChildA'

mount() {
mountA.apply(this, arguments)
}
}

class ChildB extends ElemType
{
static class = 'ChildB'

mount() {
mountB.apply(this, arguments)
}
}

class Parent extends ElemType
{
static class = 'Parent'

state = {
step : 0,
}

render() {
const props = this.props
switch(this.state.step) {
case 0:
return props.childrenA
case 1:
return new ChildA({ key : props.keyA })
case 2:
return [
new ChildA({ key : props.keyA }),
new ChildB({ key : props.keyB }),
]
}
}
}

test('test #1', t => {
mountA = sinon.spy()
mountB = sinon.spy()
const parent = Parent.render({
childrenA : [],
})

t.is(parent.toString(), '<div class="Parent"></div>')
t.is(mountA.callCount, 0)
t.is(mountB.callCount, 0)

parent.setState({ step : 1 })

t.is(parent.toString(), '<div class="Parent"><div class="ChildA"></div></div>')
t.is(mountA.callCount, 1)
t.is(mountB.callCount, 0)

parent.setState({ step : 2 })

t.is(parent.toString(), '<div class="Parent"><div class="ChildA"></div><div class="ChildB"></div></div>')
t.is(mountA.callCount, 1)
t.is(mountB.callCount, 1)
})

test('test #2', t => {
mountA = sinon.spy()
mountB = sinon.spy()
const parent = Parent.render({
childrenA : undefined,
keyA : 'A',
keyB : 'B',
})

t.is(parent.toString(), '<div class="Parent"></div>')
t.is(mountA.callCount, 0)
t.is(mountB.callCount, 0)

parent.setState({ step : 1 })

t.is(parent.toString(), '<div class="Parent"><div class="ChildA"></div></div>')
t.is(mountA.callCount, 1)
t.is(mountB.callCount, 0)

parent.setState({ step : 2 })

t.is(parent.toString(), '<div class="Parent"><div class="ChildA"></div><div class="ChildB"></div></div>')
t.is(mountA.callCount, 1)
t.is(mountB.callCount, 1)
})
118 changes: 118 additions & 0 deletions test/mount-2.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import test from 'ava'
import sinon from 'sinon'
import { ElemType } from '../index.js'

let mountA, mountB

class ChildA extends ElemType
{
static class = 'ChildA'

mount() {
mountA.apply(this, arguments)
}
}

class ChildB extends ElemType
{
static class = 'ChildB'

mount() {
mountB.apply(this, arguments)
}
}

class ParentA extends ElemType
{
static class = 'ParentA'
}

class ParentB extends ElemType
{
static class = 'ParentB'

render() {
const props = this.props
switch(props.step) {
case 1:
return new ChildA({ key : props.keyA })
case 2:
return [
new ChildA({ key : props.keyA }),
new ChildB({ key : props.keyB }),
]
}
}
}

class App extends ElemType
{
static class = 'App'

state = {
step : 0,
}

render() {
const props = this.props
const step = this.state.step
switch(step) {
case 0:
return new ParentA
case 1:
case 2:
return new ParentB({
keyA : props.keyA,
keyB : props.keyB,
step,
})
}
}
}

test('test #1', t => {
mountA = sinon.spy()
mountB = sinon.spy()
const app = App.render()

t.is(app.toString(), '<div class="App"><div class="ParentA"></div></div>')
t.is(mountA.callCount, 0)
t.is(mountB.callCount, 0)

app.setState({ step : 1 })

t.is(app.toString(), '<div class="App"><div class="ParentB"><div class="ChildA"></div></div></div>')
t.is(mountA.callCount, 1)
t.is(mountB.callCount, 0)

app.setState({ step : 2 })

t.is(app.toString(), '<div class="App"><div class="ParentB"><div class="ChildA"></div><div class="ChildB"></div></div></div>')
t.is(mountA.callCount, 1)
t.is(mountB.callCount, 1)
})

test('test #2', t => {
mountA = sinon.spy()
mountB = sinon.spy()
const app = App.render({
keyA : 'A',
keyB : 'B',
})

t.is(app.toString(), '<div class="App"><div class="ParentA"></div></div>')
t.is(mountA.callCount, 0)
t.is(mountB.callCount, 0)

app.setState({ step : 1 })

t.is(app.toString(), '<div class="App"><div class="ParentB"><div class="ChildA"></div></div></div>')
t.is(mountA.callCount, 1)
t.is(mountB.callCount, 0)

app.setState({ step : 2 })

t.is(app.toString(), '<div class="App"><div class="ParentB"><div class="ChildA"></div><div class="ChildB"></div></div></div>')
t.is(mountA.callCount, 1)
t.is(mountB.callCount, 1)
})
85 changes: 0 additions & 85 deletions test/mount.test.js

This file was deleted.

0 comments on commit fa0e198

Please sign in to comment.