diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js index 70145d735..f8490d0a4 100644 --- a/src/dom_components/model/Component.js +++ b/src/dom_components/model/Component.js @@ -586,6 +586,17 @@ const Component = Backbone.Model.extend(Styleable).extend( return classStr ? classStr.split(' ') : []; }, + __logSymbol(type, toUp, opts = {}) { + // const symbols = this.__getSymbols(); + // console.log(`symbols:${type}`, + // this.cid, + // 'hasSymbol', this.__getSymbol() ? 'Y' : 'N', + // 'hasSymbols', symbols ? symbols.length : 'N', + // 'toUp', toUp.map(i => i.cid), + // 'opts', opts + // ); + }, + __initSymb() { if (this.__symbReady) return; this.on('change', this.__upSymbProps); @@ -645,7 +656,7 @@ const Component = Backbone.Model.extend(Styleable).extend( return result; }, - __upSymbProps() { + __upSymbProps(m, opts = {}) { const changed = this.changedAttributes(); const attrs = changed.attributes || {}; delete changed.status; @@ -655,20 +666,25 @@ const Component = Backbone.Model.extend(Styleable).extend( delete changed.attributes; delete attrs.id; if (!isEmptyObj(attrs)) changed.attributes = attrs; - !isEmptyObj(changed) && - this.__getSymbToUp().forEach(child => child.set(changed)); + if (!isEmptyObj(changed)) { + const toUp = this.__getSymbToUp(opts); + this.__logSymbol('props', toUp, { opts, changed }); + toUp.forEach(child => child.set(changed, opts)); + } }, - __upSymbCls() { - this.__getSymbToUp().forEach(child => { - child.set({ classes: this.get('classes') }); + __upSymbCls(m, c, opts = {}) { + const toUp = this.__getSymbToUp(); + this.__logSymbol('classes', toUp, { opts }); + toUp.forEach(child => { + // This will propagate the change up to __upSymbProps + child.set('classes', this.get('classes'), { fromInstance: this }); }); }, __upSymbComps(m, c, o) { const { fromInstance } = o || c || {}; - const useMain = !fromInstance; - const toUpOpts = { useMain, fromInstance }; + const toUpOpts = { fromInstance }; if (!o) { // Reset @@ -753,7 +769,7 @@ const Component = Backbone.Model.extend(Styleable).extend( } }, - initClasses() { + initClasses(m, c, opts = {}) { const event = 'change:classes'; const attrCls = this.get('attributes').class || []; const toListen = [this, event, this.initClasses]; @@ -762,7 +778,7 @@ const Component = Backbone.Model.extend(Styleable).extend( this.stopListening(...toListen); const classes = this.normalizeClasses(clsArr); const selectors = new Selectors([]); - this.set('classes', selectors); + this.set('classes', selectors, opts); selectors.add(classes); selectors.on('add remove reset', this.__upSymbCls); this.listenTo(...toListen); diff --git a/test/specs/dom_components/model/Symbols.js b/test/specs/dom_components/model/Symbols.js index 3924717f5..7ff8fc87d 100644 --- a/test/specs/dom_components/model/Symbols.js +++ b/test/specs/dom_components/model/Symbols.js @@ -117,4 +117,83 @@ describe('Symbols', () => { // The new main Symbol should keep the track of all instances expect(symbAdded.__getSymbols().length).toBe(allInst.length); }); + + describe('Creating 3 symbols in the wrapper', () => { + let allInst, all, comp, symbol, compInitChild; + beforeEach(() => { + comp = wrapper.append(compMultipleNodes)[0]; + compInitChild = comp.components().length; + symbol = createSymbol(comp); + const comp2 = createSymbol(comp); + const comp3 = createSymbol(comp); + allInst = [comp, comp2, comp3]; + all = [...allInst, symbol]; + }); + + afterEach(() => { + wrapper.components().reset(); + }); + + test('The wrapper contains all the symbols', () => { + expect(wrapper.components().length).toBe(all.length); + }); + + test('All symbols contain the same amount of children', () => { + all.forEach(cmp => expect(cmp.components().length).toBe(compInitChild)); + }); + + test('Adding a new component to a symbol, it will be propogated to all instances', () => { + symbol.append(simpleComp, { at: 0 })[0]; + all.forEach(cmp => + expect(cmp.components().length).toBe(compInitChild + 1) + ); + }); + + test('Adding a new component to an instance of the symbol, it will be propogated to all symbols', () => { + comp.append(simpleComp, { at: 0 })[0]; + all.forEach(cmp => + expect(cmp.components().length).toBe(compInitChild + 1) + ); + }); + + test('Moving a new added component, will propagate the action in all symbols', () => { + const added = comp.append(simpleComp)[0]; + const newChildLen = compInitChild + 1; + added.move(comp, { at: 0 }); + const symbRef = added.__getSymbol(); + // All symbols still have the same amount of components + all.forEach(cmp => expect(cmp.components().length).toBe(newChildLen)); + // All instances refer to the same symbol + allInst.forEach(cmp => + expect( + cmp + .components() + .at(0) + .__getSymbol() + ).toBe(symbRef) + ); + // The moved symbol contains all its instances + expect( + symbol + .components() + .at(0) + .__getSymbols().length + ).toBe(allInst.length); + }); + + test('Adding a class, reflects changes to all symbols', () => { + const initSel = symbol.getSelectorsString(); + all.forEach(cmp => expect(cmp.getSelectorsString()).toBe(initSel)); + // Adding a class to a symbol + symbol.addClass('myclass'); + const newSel = symbol.getSelectorsString(); + expect(newSel).not.toBe(initSel); + all.forEach(cmp => expect(cmp.getSelectorsString()).toBe(newSel)); + // Adding a class to an instance + comp.addClass('myclass2'); + const newSel2 = comp.getSelectorsString(); + expect(newSel2).not.toBe(newSel); + all.forEach(cmp => expect(cmp.getSelectorsString()).toBe(newSel2)); + }); + }); });