diff --git a/modules/change_detection/src/change_detector.js b/modules/change_detection/src/change_detector.js index b5888c9cea..f2e223e616 100644 --- a/modules/change_detection/src/change_detector.js +++ b/modules/change_detection/src/change_detector.js @@ -1,19 +1,20 @@ -import {ProtoWatchGroup, WatchGroup} from './watch_group'; +import {ProtoRecordRange, RecordRange} from './record_range'; import {ProtoRecord, Record} from './record'; import {FIELD, int, isPresent} from 'facade/lang'; + export * from './record'; -export * from './watch_group' +export * from './record_range' export class ChangeDetector { - @FIELD('final _rootWatchGroup:WatchGroup') - constructor(watchGroup:WatchGroup) { - this._rootWatchGroup = watchGroup; + @FIELD('final _rootRecordRange:RecordRange') + constructor(recordRange:RecordRange) { + this._rootRecordRange = recordRange; } detectChanges():int { var count:int = 0; - for (var record = this._rootWatchGroup.findFirstEnabledRecord(); + for (var record = this._rootRecordRange.findFirstEnabledRecord(); isPresent(record); record = record.nextEnabled) { if (record.check()) { diff --git a/modules/change_detection/src/record.js b/modules/change_detection/src/record.js index 7e94ae98f2..d8e8f1df69 100644 --- a/modules/change_detection/src/record.js +++ b/modules/change_detection/src/record.js @@ -1,4 +1,4 @@ -import {ProtoWatchGroup, WatchGroup} from './watch_group'; +import {ProtoRecordRange, RecordRange} from './record_range'; import {FIELD, isPresent, isBlank, int, StringWrapper, FunctionWrapper, BaseException} from 'facade/lang'; import {ListWrapper, MapWrapper} from 'facade/collection'; import {ClosureMap} from 'change_detection/parser/closure_map'; @@ -17,7 +17,7 @@ export const PROTO_RECORD_PROPERTY = 'property'; * real world numbers show that it does not provide significant benefits. */ export class ProtoRecord { - @FIELD('final watchGroup:wg.ProtoWatchGroup') + @FIELD('final recordRange:ProtoRecordRange') @FIELD('final context:Object') @FIELD('final funcOrValue:Object') @FIELD('final arity:int') @@ -26,13 +26,13 @@ export class ProtoRecord { @FIELD('next:ProtoRecord') @FIELD('prev:ProtoRecord') @FIELD('recordInConstruction:Record') - constructor(watchGroup:ProtoWatchGroup, + constructor(recordRange:ProtoRecordRange, recordType:string, funcOrValue, arity:int, dest) { - this.watchGroup = watchGroup; + this.recordRange = recordRange; this.recordType = recordType; this.funcOrValue = funcOrValue; this.arity = arity; @@ -61,7 +61,7 @@ export class ProtoRecord { * - Keep this object as lean as possible. (Lean in number of fields) */ export class Record { - @FIELD('final watchGroup:WatchGroup') + @FIELD('final recordRange:RecordRange') @FIELD('final protoRecord:ProtoRecord') @FIELD('next:Record') @FIELD('prev:Record') @@ -86,8 +86,8 @@ export class Record { // Otherwise it is the context used by WatchGroupDispatcher. @FIELD('dest') - constructor(watchGroup:WatchGroup, protoRecord:ProtoRecord, formatters:Map) { - this.watchGroup = watchGroup; + constructor(recordRange:RecordRange, protoRecord:ProtoRecord, formatters:Map) { + this.recordRange = recordRange; this.protoRecord = protoRecord; this.next = null; @@ -140,8 +140,8 @@ export class Record { } } - static createMarker(wg:WatchGroup) { - var r = new Record(wg, null, null); + static createMarker(rr:RecordRange) { + var r = new Record(rr, null, null); r.disabled = true; return r; } @@ -166,7 +166,7 @@ export class Record { this.dest.updateContext(this.currentValue); } } else { - this.watchGroup.dispatcher.onRecordChange(this, this.protoRecord.dest); + this.recordRange.dispatcher.onRecordChange(this, this.protoRecord.dest); } } @@ -183,11 +183,11 @@ export class Record { return FunctionWrapper.apply(this.context, this.args); case MODE_STATE_INVOKE_PURE_FUNCTION: - this.watchGroup.disableRecord(this); + this.recordRange.disableRecord(this); return FunctionWrapper.apply(this.funcOrValue, this.args); case MODE_STATE_CONST: - this.watchGroup.disableRecord(this); + this.recordRange.disableRecord(this); return this.funcOrValue; case MODE_STATE_MARKER: @@ -206,18 +206,18 @@ export class Record { updateArg(value, position:int) { this.args[position] = value; - this.watchGroup.enableRecord(this); + this.recordRange.enableRecord(this); } updateContext(value) { this.context = value; - if (! this.isMarkerRecord) { - this.watchGroup.enableRecord(this); + if (!this.isMarkerRecord) { + this.recordRange.enableRecord(this); } } get isMarkerRecord() { - return isBlank(this.protoRecord); + return this.mode == MODE_STATE_MARKER; } } diff --git a/modules/change_detection/src/watch_group.js b/modules/change_detection/src/record_range.js similarity index 81% rename from modules/change_detection/src/watch_group.js rename to modules/change_detection/src/record_range.js index 44ac0c28df..d686c157c7 100644 --- a/modules/change_detection/src/watch_group.js +++ b/modules/change_detection/src/record_range.js @@ -6,7 +6,8 @@ import {AST, AccessMember, ImplicitReceiver, AstVisitor, LiteralPrimitive, Binary, Formatter, MethodCall, FunctionCall, PrefixNot, Conditional, LiteralArray, LiteralMap, KeyedAccess, Chain, Assignment} from './parser/ast'; -export class ProtoWatchGroup { + +export class ProtoRecordRange { @FIELD('headRecord:ProtoRecord') @FIELD('tailRecord:ProtoRecord') constructor() { @@ -15,14 +16,14 @@ export class ProtoWatchGroup { } /** - * Parses [ast] into [ProtoRecord]s and adds them to [ProtoWatchGroup]. + * Parses [ast] into [ProtoRecord]s and adds them to [ProtoRecordRange]. * * @param ast The expression to watch * @param memento an opaque object which will be passed to WatchGroupDispatcher on * detecting a change. * @param shallow Should collections be shallow watched */ - watch(ast:AST, + addRecordsFromAST(ast:AST, memento, shallow = false) { @@ -45,20 +46,20 @@ export class ProtoWatchGroup { // TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher. // but @Implements is not ready yet. - instantiate(dispatcher, formatters:Map):WatchGroup { - var watchGroup:WatchGroup = new WatchGroup(this, dispatcher); + instantiate(dispatcher, formatters:Map):RecordRange { + var recordRange:RecordRange = new RecordRange(this, dispatcher); if (this.headRecord !== null) { - this._createRecords(watchGroup, formatters); + this._createRecords(recordRange, formatters); this._setDestination(); } - return watchGroup; + return recordRange; } - _createRecords(watchGroup:WatchGroup, formatters:Map) { + _createRecords(recordRange:RecordRange, formatters:Map) { for (var proto = this.headRecord; proto != null; proto = proto.next) { - var record = new Record(watchGroup, proto, formatters); + var record = new Record(recordRange, proto, formatters); proto.recordInConstruction = record; - watchGroup.addRecord(record); + recordRange.addRecord(record); } } @@ -72,16 +73,16 @@ export class ProtoWatchGroup { } } -export class WatchGroup { - @FIELD('final protoWatchGroup:ProtoWatchGroup') +export class RecordRange { + @FIELD('final protoRecordRange:ProtoRecordRange') @FIELD('final dispatcher:WatchGroupDispatcher') @FIELD('final headRecord:Record') @FIELD('final tailRecord:Record') @FIELD('final disabled:boolean') // TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher. // but @Implements is not ready yet. - constructor(protoWatchGroup:ProtoWatchGroup, dispatcher) { - this.protoWatchGroup = protoWatchGroup; + constructor(protoRecordRange:ProtoRecordRange, dispatcher) { + this.protoRecordRange = protoRecordRange; this.dispatcher = dispatcher; this.disabled = false; @@ -89,64 +90,53 @@ export class WatchGroup { this.headRecord = Record.createMarker(this); this.tailRecord = Record.createMarker(this); - this.headRecord.next = this.tailRecord; - this.tailRecord.prev = this.headRecord; + _glue(this.headRecord, this.tailRecord); } - /// addRecord assumes that all records are enabled + /// addRecord assumes that the record is newly created, so it is enabled. addRecord(record:Record) { var lastRecord = this.tailRecord.prev; - lastRecord.next = record; - lastRecord.nextEnabled = record; - - record.prev = lastRecord; - record.prevEnabled = lastRecord; - - record.next = this.tailRecord; - this.tailRecord.prev = record; + _glue(lastRecord, record); + _glueEnabled(lastRecord, record); + _glue(record, this.tailRecord); } - addChild(child:WatchGroup) { + addRange(child:RecordRange) { var lastRecord = this.tailRecord.prev; var lastEnabledRecord = this.findLastEnabledRecord(); var firstEnabledChildRecord = child.findFirstEnabledRecord(); - lastRecord.next = child.headRecord; - child.headRecord.prev = lastRecord; - - child.tailRecord.next = this.tailRecord; - this.tailRecord.prev = child.tailRecord; + _glue(lastRecord, child.headRecord); + _glue(child.tailRecord, this.tailRecord); if (isPresent(lastEnabledRecord) && isPresent(firstEnabledChildRecord)) { - lastEnabledRecord.nextEnabled = firstEnabledChildRecord; - firstEnabledChildRecord.prevEnabled = lastEnabledRecord; + _glueEnabled(lastEnabledRecord, firstEnabledChildRecord); } } - removeChild(child:WatchGroup) { + removeRange(child:RecordRange) { var firstEnabledChildRecord = child.findFirstEnabledRecord(); var lastEnabledChildRecord = child.findLastEnabledRecord(); var next = child.tailRecord.next; var prev = child.headRecord.prev; - next.prev = prev; - prev.next = next; + _glue(prev, next); var nextEnabled = lastEnabledChildRecord.nextEnabled; var prevEnabled = firstEnabledChildRecord.prevEnabled; - if (isPresent(nextEnabled)) nextEnabled.prev = prevEnabled; - if (isPresent(prevEnabled)) prevEnabled.next = nextEnabled; + if (isPresent(nextEnabled)) nextEnabled.prevEnabled = prevEnabled; + if (isPresent(prevEnabled)) prevEnabled.nextEnabled = nextEnabled; } findFirstEnabledRecord() { - return this._nextEnabled(this.headRecord); + return this._nextEnabledInCurrentRange(this.headRecord); } findLastEnabledRecord() { - return this._prevEnabled(this.tailRecord); + return this._prevEnabledInCurrentRange(this.tailRecord); } disableRecord(record:Record) { @@ -162,8 +152,8 @@ export class WatchGroup { enableRecord(record:Record) { if (!record.disabled) return; - var prevEnabled = this._prevEnabled(record); - var nextEnabled = this._nextEnabled(record); + var prevEnabled = this._prevEnabledInCurrentRange(record); + var nextEnabled = this._nextEnabledInCurrentRange(record); record.prevEnabled = prevEnabled; record.nextEnabled = nextEnabled; @@ -174,7 +164,7 @@ export class WatchGroup { record.disabled = false; } - disableGroup(child:WatchGroup) { + disableRange(child:RecordRange) { var firstEnabledChildRecord = child.findFirstEnabledRecord(); var lastEnabledChildRecord = child.findLastEnabledRecord(); @@ -187,31 +177,32 @@ export class WatchGroup { child.disabled = true; } - enableGroup(child:WatchGroup) { - var prevEnabledRecord = this._prevEnabled(child.headRecord); - var nextEnabledRecord = this._nextEnabled(child.tailRecord); + enableRange(child:RecordRange) { + var prevEnabledRecord = this._prevEnabledInCurrentRange(child.headRecord); + var nextEnabledRecord = this._nextEnabledInCurrentRange(child.tailRecord); var firstEnabledChildRecord = child.findFirstEnabledRecord(); var lastEnabledChildRecord = child.findLastEnabledRecord(); if (isPresent(firstEnabledChildRecord) && isPresent(prevEnabledRecord)){ - firstEnabledChildRecord.prevEnabled = prevEnabledRecord; - prevEnabledRecord.nextEnabled = firstEnabledChildRecord; + _glueEnabled(prevEnabledRecord, firstEnabledChildRecord); } if (isPresent(lastEnabledChildRecord) && isPresent(nextEnabledRecord)){ - lastEnabledChildRecord.nextEnabled = nextEnabledRecord; - nextEnabledRecord.prevEnabled = lastEnabledChildRecord; + _glueEnabled(lastEnabledChildRecord, nextEnabledRecord); } child.disabled = false; } - _nextEnabled(record:Record) { + /// Returns the next enabled record in the current range. If no such record, returns null. + _nextEnabledInCurrentRange(record:Record) { + if (record === this.tailRecord) return null; + record = record.next; while (isPresent(record) && record !== this.tailRecord && record.disabled) { - if (record.isMarkerRecord && record.watchGroup.disabled) { - record = record.watchGroup.tailRecord.next; + if (record.isMarkerRecord && record.recordRange.disabled) { + record = record.recordRange.tailRecord.next; } else { record = record.next; } @@ -219,11 +210,14 @@ export class WatchGroup { return record === this.tailRecord ? null : record; } - _prevEnabled(record:Record) { + /// Returns the prev enabled record in the current range. If no such record, returns null. + _prevEnabledInCurrentRange(record:Record) { + if (record === this.headRecord) return null; + record = record.prev; while (isPresent(record) && record !== this.headRecord && record.disabled) { - if (record.isMarkerRecord && record.watchGroup.disabled) { - record = record.watchGroup.headRecord.prev; + if (record.isMarkerRecord && record.recordRange.disabled) { + record = record.recordRange.headRecord.prev; } else { record = record.prev; } @@ -233,10 +227,10 @@ export class WatchGroup { /** * Sets the context (the object) on which the change detection expressions will - * dereference themselves on. Since the WatchGroup can be reused the context - * can be re-set many times during the lifetime of the WatchGroup. + * dereference themselves on. Since the RecordRange can be reused the context + * can be re-set many times during the lifetime of the RecordRange. * - * @param context the new context for change detection for the current WatchGroup + * @param context the new context for change detection for the current RecordRange */ setContext(context) { for (var record:Record = this.headRecord; @@ -248,6 +242,16 @@ export class WatchGroup { } } +function _glue(a:Record, b:Record) { + a.next = b; + b.prev = a; +} + +function _glueEnabled(a:Record, b:Record) { + a.nextEnabled = b; + b.prevEnabled = a; +} + export class WatchGroupDispatcher { // The record holds the previous value at the time of the call onRecordChange(record:Record, context) {} @@ -264,11 +268,11 @@ class Destination { @IMPLEMENTS(AstVisitor) class ProtoRecordCreator { - @FIELD('final protoWatchGroup:ProtoWatchGroup') + @FIELD('final protoRecordRange:ProtoRecordRange') @FIELD('headRecord:ProtoRecord') @FIELD('tailRecord:ProtoRecord') - constructor(protoWatchGroup) { - this.protoWatchGroup = protoWatchGroup; + constructor(protoRecordRange) { + this.protoRecordRange = protoRecordRange; this.headRecord = null; this.tailRecord = null; } @@ -365,7 +369,7 @@ class ProtoRecordCreator { } construct(recordType, funcOrValue, arity, dest) { - return new ProtoRecord(this.protoWatchGroup, recordType, funcOrValue, arity, dest); + return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity, dest); } add(protoRecord:ProtoRecord) { diff --git a/modules/change_detection/test/change_detector_spec.js b/modules/change_detection/test/change_detector_spec.js index 63de35197c..e54e1de46f 100644 --- a/modules/change_detection/test/change_detector_spec.js +++ b/modules/change_detection/test/change_detector_spec.js @@ -8,8 +8,8 @@ import {ClosureMap} from 'change_detection/parser/closure_map'; import { ChangeDetector, - ProtoWatchGroup, - WatchGroup, + ProtoRecordRange, + RecordRange, WatchGroupDispatcher, ProtoRecord } from 'change_detection/change_detector'; @@ -23,14 +23,14 @@ export function main() { } function createChangeDetector(memo:string, exp:string, context = null, formatters = null) { - var pwg = new ProtoWatchGroup(); - pwg.watch(ast(exp), memo, false); + var prr = new ProtoRecordRange(); + prr.addRecordsFromAST(ast(exp), memo, false); var dispatcher = new LoggingDispatcher(); - var wg = pwg.instantiate(dispatcher, formatters); - wg.setContext(context); + var rr = prr.instantiate(dispatcher, formatters); + rr.setContext(context); - var cd = new ChangeDetector(wg); + var cd = new ChangeDetector(rr); return {"changeDetector" : cd, "dispatcher" : dispatcher}; } diff --git a/modules/change_detection/test/record_range_spec.js b/modules/change_detection/test/record_range_spec.js new file mode 100644 index 0000000000..e1f5ab64c9 --- /dev/null +++ b/modules/change_detection/test/record_range_spec.js @@ -0,0 +1,265 @@ +import {ddescribe, describe, it, iit, xit, expect, beforeEach} from 'test_lib/test_lib'; + +import {List, ListWrapper, MapWrapper} from 'facade/collection'; +import {isPresent} from 'facade/lang'; +import {Parser} from 'change_detection/parser/parser'; +import {Lexer} from 'change_detection/parser/lexer'; +import {ClosureMap} from 'change_detection/parser/closure_map'; + +import { + ChangeDetector, + ProtoRecordRange, + RecordRange, + WatchGroupDispatcher, + ProtoRecord + } from 'change_detection/change_detector'; + +import {Record} from 'change_detection/record'; + +export function main() { + function humanize(rr:RecordRange, names:List) { + var lookupName = (item) => + ListWrapper.last( + ListWrapper.find(names, (pair) => pair[0] === item)); + + var res = []; + var record = rr.findFirstEnabledRecord(); + while (isPresent(record)) { + ListWrapper.push(res, lookupName(record)); + record = record.nextEnabled; + } + return res; + } + + function createRecord(rr) { + return new Record(rr, new ProtoRecord(null, null, null, null, null), null); + } + + describe('record range', () => { + it('should add records', () => { + var rr = new RecordRange(null, null); + var record1 = createRecord(rr); + var record2 = createRecord(rr); + + rr.addRecord(record1); + rr.addRecord(record2); + + expect(humanize(rr, [ + [record1, 'record1'], + [record2, 'record2'] + ])).toEqual(['record1', 'record2']); + }); + + describe('adding/removing record ranges', () => { + var parent, child1, child2, child3; + var childRecord1, childRecord2, childRecord3; + var recordNames; + + beforeEach(() => { + parent = new RecordRange(null, null); + + child1 = new RecordRange(null, null); + childRecord1 = createRecord(child1); + child1.addRecord(childRecord1); + + child2 = new RecordRange(null, null); + childRecord2 = createRecord(child2); + child2.addRecord(childRecord2); + + child3 = new RecordRange(null, null); + childRecord3 = createRecord(child3); + child3.addRecord(childRecord3); + + recordNames = [ + [childRecord1, 'record1'], + [childRecord2, 'record2'], + [childRecord3, 'record3'] + ]; + }); + + it('should add record ranges', () => { + parent.addRange(child1); + parent.addRange(child2); + + expect(humanize(parent, recordNames)).toEqual(['record1', 'record2']); + }); + + it('should add nested record ranges', () => { + parent.addRange(child1); + child1.addRange(child2); + + expect(humanize(parent, recordNames)).toEqual(['record1', 'record2']); + }); + + it('should remove record ranges', () => { + parent.addRange(child1); + parent.addRange(child2); + + parent.removeRange(child1); + + expect(humanize(parent, recordNames)).toEqual(['record2']); + + parent.removeRange(child2); + + expect(humanize(parent, recordNames)).toEqual([]); + }); + + it('should remove a record range surrounded by other ranges', () => { + parent.addRange(child1); + parent.addRange(child2); + parent.addRange(child3); + + parent.removeRange(child2); + + expect(humanize(parent, recordNames)).toEqual(['record1', 'record3']); + }); + }); + + describe('enabling/disabling records', () => { + var rr; + var record1, record2, record3, record4; + var recordNames; + + beforeEach(() => { + rr = new RecordRange(null, null); + record1 = createRecord(rr); + record2 = createRecord(rr); + record3 = createRecord(rr); + record4 = createRecord(rr); + + recordNames = [ + [record1, 'record1'], + [record2, 'record2'], + [record3, 'record3'], + [record4, 'record4'] + ]; + }); + + it('should disable a single record', () => { + rr.addRecord(record1); + + rr.disableRecord(record1); + + expect(humanize(rr, recordNames)).toEqual([]); + }); + + it('should enable a single record', () => { + rr.addRecord(record1); + rr.disableRecord(record1); + + rr.enableRecord(record1); + + expect(humanize(rr, recordNames)).toEqual(['record1']); + }); + + it('should disable a record', () => { + rr.addRecord(record1); + rr.addRecord(record2); + rr.addRecord(record3); + rr.addRecord(record4); + + rr.disableRecord(record2); + rr.disableRecord(record3); + + expect(record2.disabled).toBeTruthy(); + expect(record3.disabled).toBeTruthy(); + + expect(humanize(rr, recordNames)).toEqual(['record1', 'record4']); + }); + + it('should enable a record', () => { + rr.addRecord(record1); + rr.addRecord(record2); + rr.addRecord(record3); + rr.addRecord(record4); + rr.disableRecord(record2); + rr.disableRecord(record3); + + rr.enableRecord(record2); + rr.enableRecord(record3); + + expect(humanize(rr, recordNames)).toEqual(['record1', 'record2', 'record3', 'record4']); + }); + }); + + describe('enabling/disabling record ranges', () => { + var child1, child2, child3, child4; + var record1, record2, record3, record4; + var recordNames; + + beforeEach(() => { + child1 = new RecordRange(null, null); + record1 = createRecord(child1); + child1.addRecord(record1); + + child2 = new RecordRange(null, null); + record2 = createRecord(child2); + child2.addRecord(record2); + + child3 = new RecordRange(null, null); + record3 = createRecord(child3); + child3.addRecord(record3); + + child4 = new RecordRange(null, null); + record4 = createRecord(child4); + child4.addRecord(record4); + + recordNames = [ + [record1, 'record1'], + [record2, 'record2'], + [record3, 'record3'], + [record4, 'record4'] + ]; + }); + + it('should disable a single record range', () => { + var parent = new RecordRange(null, null); + parent.addRange(child1); + + parent.disableRange(child1); + + expect(humanize(parent, recordNames)).toEqual([]); + }); + + it('should enable a single record range', () => { + var parent = new RecordRange(null, null); + parent.addRange(child1); + parent.disableRange(child1); + + parent.enableRange(child1); + + expect(humanize(parent, recordNames)).toEqual(['record1']); + }); + + it('should disable a record range', () => { + var parent = new RecordRange(null, null); + parent.addRange(child1); + parent.addRange(child2); + parent.addRange(child3); + parent.addRange(child4); + + parent.disableRange(child2); + parent.disableRange(child3); + + expect(humanize(parent, recordNames)).toEqual(['record1', 'record4']); + }); + + it('should enable a record range', () => { + var parent = new RecordRange(null, null); + parent.addRange(child1); + parent.addRange(child2); + parent.addRange(child3); + parent.addRange(child4); + parent.disableRange(child2); + parent.disableRange(child3); + + parent.enableRange(child2); + parent.enableRange(child3); + + expect(humanize(parent, recordNames)).toEqual([ + 'record1', 'record2', 'record3', 'record4' + ]); + }); + }); + }); +} \ No newline at end of file diff --git a/modules/change_detection/test/watch_group_spec.js b/modules/change_detection/test/watch_group_spec.js deleted file mode 100644 index aea09703ba..0000000000 --- a/modules/change_detection/test/watch_group_spec.js +++ /dev/null @@ -1,243 +0,0 @@ -import {ddescribe, describe, it, iit, xit, expect, beforeEach} from 'test_lib/test_lib'; - -import {List, ListWrapper, MapWrapper} from 'facade/collection'; -import {isPresent} from 'facade/lang'; -import {Parser} from 'change_detection/parser/parser'; -import {Lexer} from 'change_detection/parser/lexer'; -import {ClosureMap} from 'change_detection/parser/closure_map'; - -import { - ChangeDetector, - ProtoWatchGroup, - WatchGroup, - WatchGroupDispatcher, - ProtoRecord - } from 'change_detection/change_detector'; - -import {Record} from 'change_detection/record'; - -export function main() { - function humanize(wg:WatchGroup, names:List) { - var lookupName = (item) => - ListWrapper.last( - ListWrapper.find(names, (pair) => pair[0] === item)); - - var res = []; - var record = wg.findFirstEnabledRecord(); - while (isPresent(record)) { - ListWrapper.push(res, lookupName(record)); - record = record.nextEnabled; - } - return res; - } - - function createRecord(wg) { - return new Record(wg, new ProtoRecord(null, null, null, null, null), null); - } - - describe('watch group', () => { - it("should add records", () => { - var wg = new WatchGroup(null, null); - var record1 = createRecord(wg); - var record2 = createRecord(wg); - - wg.addRecord(record1); - wg.addRecord(record2); - - expect(humanize(wg, [ - [record1, 'record1'], - [record2, 'record2'] - ])).toEqual(['record1', 'record2']); - }); - - describe("adding/removing child groups", () => { - var parent, child1, child2; - var childRecord1, childRecord2; - var recordNames; - - beforeEach(() => { - parent = new WatchGroup(null, null); - - child1 = new WatchGroup(null, null); - childRecord1 = createRecord(child1); - child1.addRecord(childRecord1); - - child2 = new WatchGroup(null, null); - childRecord2 = createRecord(child2); - child2.addRecord(childRecord2); - - recordNames = [ - [childRecord1, 'record1'], - [childRecord2, 'record2'], - ]; - }); - - it("should add child groups", () => { - parent.addChild(child1); - parent.addChild(child2); - - expect(humanize(parent, recordNames)).toEqual(['record1', 'record2']); - }); - - it("should remove children", () => { - parent.addChild(child1); - parent.addChild(child2); - - parent.removeChild(child1); - - expect(humanize(parent, recordNames)).toEqual(['record2']); - - parent.removeChild(child2); - - expect(humanize(parent, recordNames)).toEqual([]); - }); - }); - - describe("enabling/disabling records", () => { - var wg; - var record1, record2, record3, record4; - var recordNames; - - beforeEach(() => { - wg = new WatchGroup(null, null); - record1 = createRecord(wg); - record2 = createRecord(wg); - record3 = createRecord(wg); - record4 = createRecord(wg); - - recordNames = [ - [record1, 'record1'], - [record2, 'record2'], - [record3, 'record3'], - [record4, 'record4'] - ]; - }); - - it("should disable a single record", () => { - wg.addRecord(record1); - - wg.disableRecord(record1); - - expect(humanize(wg, recordNames)).toEqual([]); - }); - - it("should enable a single record", () => { - wg.addRecord(record1); - wg.disableRecord(record1); - - wg.enableRecord(record1); - - expect(humanize(wg, recordNames)).toEqual(['record1']); - }); - - it("should disable a record", () => { - wg.addRecord(record1); - wg.addRecord(record2); - wg.addRecord(record3); - wg.addRecord(record4); - - wg.disableRecord(record2); - wg.disableRecord(record3); - - expect(record2.disabled).toBeTruthy(); - expect(record3.disabled).toBeTruthy(); - - expect(humanize(wg, recordNames)).toEqual(['record1', 'record4']); - }); - - it("should enable a record", () => { - wg.addRecord(record1); - wg.addRecord(record2); - wg.addRecord(record3); - wg.addRecord(record4); - wg.disableRecord(record2); - wg.disableRecord(record3); - - wg.enableRecord(record2); - wg.enableRecord(record3); - - expect(humanize(wg, recordNames)).toEqual(['record1', 'record2', 'record3', 'record4']); - }); - }); - - describe("enabling/disabling child groups", () => { - var child1, child2, child3, child4; - var record1, record2, record3, record4; - var recordNames; - - beforeEach(() => { - child1 = new WatchGroup(null, null); - record1 = createRecord(child1); - child1.addRecord(record1); - - child2 = new WatchGroup(null, null); - record2 = createRecord(child2); - child2.addRecord(record2); - - child3 = new WatchGroup(null, null); - record3 = createRecord(child3); - child3.addRecord(record3); - - child4 = new WatchGroup(null, null); - record4 = createRecord(child4); - child4.addRecord(record4); - - recordNames = [ - [record1, 'record1'], - [record2, 'record2'], - [record3, 'record3'], - [record4, 'record4'] - ]; - }); - - it("should disable a single watch group", () => { - var parent = new WatchGroup(null, null); - parent.addChild(child1); - - parent.disableGroup(child1); - - expect(humanize(parent, recordNames)).toEqual([]); - }); - - it("should enable a single watch group", () => { - var parent = new WatchGroup(null, null); - parent.addChild(child1); - parent.disableGroup(child1); - - parent.enableGroup(child1); - - expect(humanize(parent, recordNames)).toEqual(['record1']); - }); - - it("should disable a watch group", () => { - var parent = new WatchGroup(null, null); - parent.addChild(child1); - parent.addChild(child2); - parent.addChild(child3); - parent.addChild(child4); - - parent.disableGroup(child2); - parent.disableGroup(child3); - - expect(humanize(parent, recordNames)).toEqual(['record1', 'record4']); - }); - - it("should enable a watch group", () => { - var parent = new WatchGroup(null, null); - parent.addChild(child1); - parent.addChild(child2); - parent.addChild(child3); - parent.addChild(child4); - parent.disableGroup(child2); - parent.disableGroup(child3); - - parent.enableGroup(child2); - parent.enableGroup(child3); - - expect(humanize(parent, recordNames)).toEqual([ - 'record1', 'record2', 'record3', 'record4' - ]); - }); - }); - }); -} \ No newline at end of file diff --git a/modules/core/src/application.js b/modules/core/src/application.js index 9a0eacbaeb..b652136b65 100644 --- a/modules/core/src/application.js +++ b/modules/core/src/application.js @@ -7,7 +7,7 @@ import {ClosureMap} from 'change_detection/parser/closure_map'; import {Parser} from 'change_detection/parser/parser'; import {Lexer} from 'change_detection/parser/lexer'; import {ChangeDetector} from 'change_detection/change_detector'; -import {WatchGroup} from 'change_detection/watch_group'; +import {RecordRange} from 'change_detection/record_range'; import {TemplateLoader} from './compiler/template_loader'; import {Reflector} from './compiler/reflector'; import {AnnotatedType} from './compiler/annotated_type'; @@ -56,7 +56,7 @@ export function documentDependentBindings(appComponentType) { }); }, [Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken]), - bind(appWatchGroupToken).toFactory((rootView) => rootView.watchGroup, + bind(appWatchGroupToken).toFactory((rootView) => rootView.recordRange, [appViewToken]), bind(ChangeDetector).toFactory((appWatchGroup) => new ChangeDetector(appWatchGroup), [appWatchGroupToken]) diff --git a/modules/core/src/compiler/pipeline/element_binder_builder.js b/modules/core/src/compiler/pipeline/element_binder_builder.js index 21233f4adf..44b45c4ecf 100644 --- a/modules/core/src/compiler/pipeline/element_binder_builder.js +++ b/modules/core/src/compiler/pipeline/element_binder_builder.js @@ -4,7 +4,7 @@ import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'facade/collection import {Parser} from 'change_detection/parser/parser'; import {ClosureMap} from 'change_detection/parser/closure_map'; -import {ProtoWatchGroup} from 'change_detection/watch_group'; +import {ProtoRecordRange} from 'change_detection/record_range'; import {Directive} from '../../annotations/directive'; import {Component} from '../../annotations/component'; @@ -20,7 +20,7 @@ import {CompileControl} from './compile_control'; /** * Creates the ElementBinders and adds watches to the - * ProtoWatchGroup. + * ProtoRecordRange. * * Fills: * - CompileElement#inheritedElementBinder diff --git a/modules/core/src/compiler/pipeline/proto_view_builder.js b/modules/core/src/compiler/pipeline/proto_view_builder.js index d5013fcea6..5ba6cec59a 100644 --- a/modules/core/src/compiler/pipeline/proto_view_builder.js +++ b/modules/core/src/compiler/pipeline/proto_view_builder.js @@ -2,7 +2,7 @@ import {isPresent, BaseException} from 'facade/lang'; import {ListWrapper, MapWrapper} from 'facade/collection'; import {ProtoView} from '../view'; -import {ProtoWatchGroup} from 'change_detection/watch_group'; +import {ProtoRecordRange} from 'change_detection/record_range'; import {CompileStep} from './compile_step'; import {CompileElement} from './compile_element'; @@ -21,7 +21,7 @@ export class ProtoViewBuilder extends CompileStep { process(parent:CompileElement, current:CompileElement, control:CompileControl) { var inheritedProtoView = null; if (current.isViewRoot) { - inheritedProtoView = new ProtoView(current.element, new ProtoWatchGroup()); + inheritedProtoView = new ProtoView(current.element, new ProtoRecordRange()); if (isPresent(parent)) { if (isPresent(parent.inheritedElementBinder.nestedProtoView)) { throw new BaseException('Only one nested view per element is allowed'); diff --git a/modules/core/src/compiler/view.js b/modules/core/src/compiler/view.js index e844cbcd8c..300acd649b 100644 --- a/modules/core/src/compiler/view.js +++ b/modules/core/src/compiler/view.js @@ -1,6 +1,6 @@ import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facade/dom'; import {ListWrapper, MapWrapper} from 'facade/collection'; -import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group'; +import {ProtoRecordRange, RecordRange, WatchGroupDispatcher} from 'change_detection/record_range'; import {Record} from 'change_detection/record'; import {AST} from 'change_detection/parser/ast'; @@ -25,7 +25,7 @@ export class View { @FIELD('final elementInjectors:List') @FIELD('final bindElements:List') @FIELD('final textNodes:List') - @FIELD('final watchGroup:WatchGroup') + @FIELD('final recordRange:RecordRange') /// When the view is part of render tree, the DocumentFragment is empty, which is why we need /// to keep track of the nodes. @FIELD('final nodes:List') @@ -33,15 +33,15 @@ export class View { @FIELD('childViews: List') constructor(nodes:List, elementInjectors:List, rootElementInjectors:List, textNodes:List, bindElements:List, - protoWatchGroup:ProtoWatchGroup, context) { + protoRecordRange:ProtoRecordRange, context) { this.nodes = nodes; this.elementInjectors = elementInjectors; this.rootElementInjectors = rootElementInjectors; this.onChangeDispatcher = null; this.textNodes = textNodes; this.bindElements = bindElements; - this.watchGroup = protoWatchGroup.instantiate(this, MapWrapper.create()); - this.watchGroup.setContext(context); + this.recordRange = protoRecordRange.instantiate(this, MapWrapper.create()); + this.recordRange.setContext(context); // TODO(rado): Since this is only used in tests for now, investigate whether // we can remove it. this.childViews = []; @@ -65,21 +65,21 @@ export class View { addChild(childView: View) { ListWrapper.push(this.childViews, childView); - this.watchGroup.addChild(childView.watchGroup); + this.recordRange.addRange(childView.recordRange); } } export class ProtoView { @FIELD('final element:Element') @FIELD('final elementBinders:List') - @FIELD('final protoWatchGroup:ProtoWatchGroup') + @FIELD('final protoRecordRange:ProtoRecordRange') constructor( template:Element, - protoWatchGroup:ProtoWatchGroup) { + protoRecordRange:ProtoRecordRange) { this.element = template; this.elementBinders = []; this.variableBindings = MapWrapper.create(); - this.protoWatchGroup = protoWatchGroup; + this.protoRecordRange = protoRecordRange; this.textNodesWithBindingCount = 0; this.elementsWithBindingCount = 0; } @@ -115,7 +115,7 @@ export class ProtoView { viewNodes = [clone]; } var view = new View(viewNodes, elementInjectors, rootElementInjectors, textNodes, - bindElements, this.protoWatchGroup, context); + bindElements, this.protoRecordRange, context); ProtoView._instantiateDirectives( view, elements, elementInjectors, lightDomAppInjector, shadowAppInjectors); @@ -145,7 +145,7 @@ export class ProtoView { elBinder.textNodeIndices = ListWrapper.create(); } ListWrapper.push(elBinder.textNodeIndices, indexInParent); - this.protoWatchGroup.watch(expression, this.textNodesWithBindingCount++); + this.protoRecordRange.addRecordsFromAST(expression, this.textNodesWithBindingCount++); } /** @@ -157,7 +157,7 @@ export class ProtoView { elBinder.hasElementPropertyBindings = true; this.elementsWithBindingCount++; } - this.protoWatchGroup.watch(expression, + this.protoRecordRange.addRecordsFromAST(expression, new ElementPropertyMemento( this.elementsWithBindingCount-1, propertyName @@ -184,7 +184,7 @@ export class ProtoView { expression:AST, setterName:string, setter:SetterFn) { - this.protoWatchGroup.watch( + this.protoRecordRange.addRecordsFromAST( expression, new DirectivePropertyMemento( this.elementBinders.length-1, @@ -288,7 +288,7 @@ export class ProtoView { // Used for bootstrapping. static createRootProtoView(protoView: ProtoView, insertionElement, rootComponentAnnotatedType: AnnotatedType): ProtoView { - var rootProtoView = new ProtoView(insertionElement, new ProtoWatchGroup()); + var rootProtoView = new ProtoView(insertionElement, new ProtoRecordRange()); var binder = rootProtoView.bindElement( new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true)); binder.componentDirective = rootComponentAnnotatedType; diff --git a/modules/core/src/core.js b/modules/core/src/core.js index 3100726be7..8744601b82 100644 --- a/modules/core/src/core.js +++ b/modules/core/src/core.js @@ -9,7 +9,7 @@ export * from './annotations/template_config'; export * from './application'; export * from 'change_detection/change_detector'; -export * from 'change_detection/watch_group'; +export * from 'change_detection/record_range'; export * from 'change_detection/record'; export * from './compiler/compiler'; diff --git a/modules/core/test/compiler/integration_spec.js b/modules/core/test/compiler/integration_spec.js index 040f654f5c..8041e55b9e 100644 --- a/modules/core/test/compiler/integration_spec.js +++ b/modules/core/test/compiler/integration_spec.js @@ -24,12 +24,12 @@ export function main() { compiler = new Compiler(null, new Reflector(), new Parser(new Lexer(), closureMap), closureMap); }); - describe('react to watch group changes', function() { + describe('react to record changes', function() { var view, ctx, cd; function createView(pv) { ctx = new MyComp(); view = pv.instantiate(ctx, new Injector([]), null); - cd = new ChangeDetector(view.watchGroup); + cd = new ChangeDetector(view.recordRange); } it('should consume text node changes', (done) => { diff --git a/modules/core/test/compiler/pipeline/element_binder_builder_spec.js b/modules/core/test/compiler/pipeline/element_binder_builder_spec.js index b904e02e83..723a38b83c 100644 --- a/modules/core/test/compiler/pipeline/element_binder_builder_spec.js +++ b/modules/core/test/compiler/pipeline/element_binder_builder_spec.js @@ -16,7 +16,7 @@ import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/ import {ProtoElementInjector} from 'core/compiler/element_injector'; import {Reflector} from 'core/compiler/reflector'; -import {ProtoWatchGroup} from 'change_detection/watch_group'; +import {ProtoRecordRange} from 'change_detection/record_range'; import {Parser} from 'change_detection/parser/parser'; import {Lexer} from 'change_detection/parser/lexer'; import {ClosureMap} from 'change_detection/parser/closure_map'; @@ -36,7 +36,7 @@ export function main() { new MockStep((parent, current, control) => { if (isPresent(current.element.getAttribute('viewroot'))) { current.isViewRoot = true; - current.inheritedProtoView = new ProtoView(current.element, new ProtoWatchGroup()); + current.inheritedProtoView = new ProtoView(current.element, new ProtoRecordRange()); } else if (isPresent(parent)) { current.inheritedProtoView = parent.inheritedProtoView; } @@ -81,7 +81,7 @@ export function main() { function instantiateView(protoView) { evalContext = new Context(); view = protoView.instantiate(evalContext, new Injector([]), null); - changeDetector = new ChangeDetector(view.watchGroup); + changeDetector = new ChangeDetector(view.recordRange); } it('should not create an ElementBinder for elements that have no bindings', () => { @@ -206,7 +206,7 @@ export function main() { var results = pipeline.process(createElement('
')); var pv = results[0].inheritedProtoView; results[0].inheritedElementBinder.nestedProtoView = new ProtoView( - createElement('
'), new ProtoWatchGroup()); + createElement('
'), new ProtoRecordRange()); instantiateView(pv); evalContext.prop1 = 'a'; diff --git a/modules/core/test/compiler/view_spec.js b/modules/core/test/compiler/view_spec.js index be0a0337ae..b43583d611 100644 --- a/modules/core/test/compiler/view_spec.js +++ b/modules/core/test/compiler/view_spec.js @@ -4,7 +4,7 @@ import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injec import {Reflector} from 'core/compiler/reflector'; import {Component} from 'core/annotations/component'; import {Decorator} from 'core/annotations/decorator'; -import {ProtoWatchGroup} from 'change_detection/watch_group'; +import {ProtoRecordRange} from 'change_detection/record_range'; import {ChangeDetector} from 'change_detection/change_detector'; import {TemplateConfig} from 'core/annotations/template_config'; import {Parser} from 'change_detection/parser/parser'; @@ -35,7 +35,7 @@ export function main() { } it('should collect the root node in the ProtoView element', () => { - var pv = new ProtoView(templateAwareCreateElement('
'), new ProtoWatchGroup()); + var pv = new ProtoView(templateAwareCreateElement('
'), new ProtoRecordRange()); var view = pv.instantiate(null, null, null); expect(view.nodes.length).toBe(1); expect(view.nodes[0].getAttribute('id')).toEqual('1'); @@ -44,7 +44,7 @@ export function main() { describe('collect elements with property bindings', () => { it('should collect property bindings on the root element if it has the ng-binding class', () => { - var pv = new ProtoView(templateAwareCreateElement('
'), new ProtoWatchGroup()); + var pv = new ProtoView(templateAwareCreateElement('
'), new ProtoRecordRange()); pv.bindElement(null); pv.bindElementProperty('prop', parser.parseBinding('a').ast); @@ -55,7 +55,7 @@ export function main() { it('should collect property bindings on child elements with ng-binding class', () => { var pv = new ProtoView(templateAwareCreateElement('
'), - new ProtoWatchGroup()); + new ProtoRecordRange()); pv.bindElement(null); pv.bindElementProperty('a', parser.parseBinding('b').ast); @@ -69,7 +69,7 @@ export function main() { describe('collect text nodes with bindings', () => { it('should collect text nodes under the root element', () => { - var pv = new ProtoView(templateAwareCreateElement('
{{}}{{}}
'), new ProtoWatchGroup()); + var pv = new ProtoView(templateAwareCreateElement('
{{}}{{}}
'), new ProtoRecordRange()); pv.bindElement(null); pv.bindTextNode(0, parser.parseBinding('a').ast); pv.bindTextNode(2, parser.parseBinding('b').ast); @@ -82,7 +82,7 @@ export function main() { it('should collect text nodes with bindings on child elements with ng-binding class', () => { var pv = new ProtoView(templateAwareCreateElement('
{{}}
'), - new ProtoWatchGroup()); + new ProtoRecordRange()); pv.bindElement(null); pv.bindTextNode(0, parser.parseBinding('b').ast); @@ -97,14 +97,14 @@ export function main() { describe('inplace instantiation', () => { it('should be supported.', () => { var template = createElement('
') - var view = new ProtoView(template, new ProtoWatchGroup()) + var view = new ProtoView(template, new ProtoRecordRange()) .instantiate(null, null, null, true); expect(view.nodes[0]).toBe(template); }); it('should be off by default.', () => { var template = createElement('
') - var view = new ProtoView(template, new ProtoWatchGroup()) + var view = new ProtoView(template, new ProtoRecordRange()) .instantiate(null, null, null); expect(view.nodes[0]).not.toBe(template); }); @@ -120,7 +120,7 @@ export function main() { describe('create ElementInjectors', () => { it('should use the directives of the ProtoElementInjector', () => { - var pv = new ProtoView(createElement('
'), new ProtoWatchGroup()); + var pv = new ProtoView(createElement('
'), new ProtoRecordRange()); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); var view = pv.instantiate(null, null, null); @@ -130,7 +130,7 @@ export function main() { it('should use the correct parent', () => { var pv = new ProtoView(createElement('
'), - new ProtoWatchGroup()); + new ProtoRecordRange()); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); pv.bindElement(protoParent); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); @@ -146,7 +146,7 @@ export function main() { it('should collect a single root element injector', () => { var pv = new ProtoView(createElement('
'), - new ProtoWatchGroup()); + new ProtoRecordRange()); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); pv.bindElement(protoParent); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); @@ -158,7 +158,7 @@ export function main() { it('should collect multiple root element injectors', () => { var pv = new ProtoView(createElement('
'), - new ProtoWatchGroup()); + new ProtoRecordRange()); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective])); @@ -174,7 +174,7 @@ export function main() { var view, ctx; function createComponentWithSubPV(subProtoView) { - var pv = new ProtoView(createElement(''), new ProtoWatchGroup()); + var pv = new ProtoView(createElement(''), new ProtoRecordRange()); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true)); binder.componentDirective = someComponentDirective; binder.nestedProtoView = subProtoView; @@ -187,7 +187,7 @@ export function main() { } it('should create shadow dom', () => { - var subpv = new ProtoView(createElement('hello shadow dom'), new ProtoWatchGroup()); + var subpv = new ProtoView(createElement('hello shadow dom'), new ProtoRecordRange()); var pv = createComponentWithSubPV(subpv); var view = createNestedView(pv); @@ -196,7 +196,7 @@ export function main() { }); it('should expose component services to the component', () => { - var subpv = new ProtoView(createElement(''), new ProtoWatchGroup()); + var subpv = new ProtoView(createElement(''), new ProtoRecordRange()); var pv = createComponentWithSubPV(subpv); var view = createNestedView(pv); @@ -208,7 +208,7 @@ export function main() { it('should expose component services and component instance to directives in the shadow Dom', () => { var subpv = new ProtoView( - createElement('
hello shadow dom
'), new ProtoWatchGroup()); + createElement('
hello shadow dom
'), new ProtoRecordRange()); var subBinder = subpv.bindElement( new ProtoElementInjector(null, 0, [ServiceDependentDecorator])); var pv = createComponentWithSubPV(subpv); @@ -226,18 +226,18 @@ export function main() { }); }); - describe('react to watch group changes', () => { + describe('react to record changes', () => { var view, cd, ctx; function createView(protoView) { ctx = new MyEvaluationContext(); view = protoView.instantiate(ctx, null, null); - cd = new ChangeDetector(view.watchGroup); + cd = new ChangeDetector(view.recordRange); } it('should consume text node changes', () => { var pv = new ProtoView(createElement('
{{}}
'), - new ProtoWatchGroup()); + new ProtoRecordRange()); pv.bindElement(null); pv.bindTextNode(0, parser.parseBinding('foo').ast); createView(pv); @@ -249,7 +249,7 @@ export function main() { it('should consume element binding changes', () => { var pv = new ProtoView(createElement('
'), - new ProtoWatchGroup()); + new ProtoRecordRange()); pv.bindElement(null); pv.bindElementProperty('id', parser.parseBinding('foo').ast); createView(pv); @@ -261,7 +261,7 @@ export function main() { it('should consume directive watch expression change.', () => { var pv = new ProtoView(createElement('
'), - new ProtoWatchGroup()); + new ProtoRecordRange()); pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective])); pv.bindDirectiveProperty( 0, parser.parseBinding('foo').ast, 'prop', closureMap.setter('prop')); createView(pv); @@ -277,7 +277,7 @@ export function main() { var el, pv; beforeEach(() => { el = DOM.createElement('div'); - pv = new ProtoView(createElement('
hi
'), new ProtoWatchGroup()); + pv = new ProtoView(createElement('
hi
'), new ProtoRecordRange()); }); it('should create the root component when instantiated', () => {