text: define([
  	'intern!object',
  	'intern/chai!assert',
  	'../../../parser',
  	'dojo/dom-construct',
  	'dojo/_base/array',
  	'dojo/aspect',
  	'dojo/_base/declare',
  	'dojo/dom',
  	'dojo/dom-attr',
  	'dojo/_base/lang',
  	'dojo/on',
  	'dojo/_base/window',
  	'dojo/date/stamp',
  	'dojo/Stateful',
  	'dojo/Evented',
  	'dojo/text!./parser.html',
  	'./support/util'
  ], function (
  	registerSuite,
  	assert,
  	parser,
  	domConstruct,
  	array,
  	aspect,
  	declare,
  	dom,
  	domAttr,
  	lang,
  	on,
  	win,
  	dstamp,
  	Stateful,
  	Evented,
  	template,
  	util
  ) {
  	var container;
  
  	/* global tests */
  	function setup(id, shouldReturn) {
  		return function () {
  			var MyNonDojoClass = window.MyNonDojoClass = function () {};
  			MyNonDojoClass.extend = function () {
  				var args = arguments;
  				return function () {
  					this.expectedClass = true;
  					this.params = args;
  				};
  			};
  
  			declare('tests.parser.Widget', null, {
  				constructor: function (args) {
  					this.params = args;
  				}
  			});
  
  			declare('tests.parser.Class1', null, {
  				constructor: function (args) {
  					this.params = args;
  					lang.mixin(this, args);
  				},
  				preambleTestProp: 1,
  				preamble: function () {
  					this.preambleTestProp++;
  				},
  				intProp: 1,
  				callCount: 0, // for connect testing
  				callInc: function () { this.callCount++; },
  				callCount2: 0, // for assignment testing
  				strProp1: 'original1',
  				strProp2: 'original2',
  				arrProp: [],
  				arrProp2: ['foo'],
  				boolProp1: false,
  				boolProp2: true,
  				boolProp3: false,
  				boolProp4: true,
  				dateProp1: dstamp.fromISOString('2007-01-01'),
  				dateProp2: dstamp.fromISOString('2007-01-01'),
  				dateProp3: dstamp.fromISOString('2007-01-01'),
  				funcProp: function () {},
  				funcProp2: function () {},
  				funcProp3: function () {},
  				onclick: function () { this.prototypeOnclick = true; }
  				// FIXME: have to test dates!!
  				// FIXME: need to test the args property!!
  			});
  
  			declare('tests.parser.Class2', null, {
  				constructor: function () {
  					this.fromMarkup = false;
  				},
  				fromMarkup: false,
  				markupFactory: function () {
  					var i = new tests.parser.Class2();
  					i.fromMarkup = true;
  					return i;
  				}
  			});
  
  			declare('tests.parser.Class3', tests.parser.Class2, {
  				fromMarkup: false,
  				markupFactory: function (args, node, ClassCtor) {
  					var i = new ClassCtor();
  					i.classCtor = ClassCtor;
  					i.params = args;
  					return i;
  				}
  			});
  
  			declare('tests.parser.InputClass', null, {
  				constructor: function (args) {
  					this.params = args;
  					lang.mixin(this, args);
  				},
  
  				// these attributes are special in HTML, they don't have a value specified
  				disabled: false,
  				readonly: false,
  				checked: false,
  
  				// other attributes native to HTML
  				value: 'default value',
  				title: 'default title',
  				tabIndex: '0',		// special because mixed case
  
  				// custom widget attributes that don't match a native HTML attributes
  				custom1: 123,
  				custom2: 456
  			});
  
  			// Test that dir, lang, etc. attributes can be inherited from ancestor node
  			declare('tests.parser.BidiClass', tests.parser.Widget, {
  				constructor: function (args) { lang.mixin(this, args); },
  				dir: '',
  				lang: '',
  				textdir: '',
  				name: ''
  			});
  
  			// For testing that parser recurses correctly, except when the prototype has a
  			// stopParser flag
  			declare('tests.parser.NormalContainer', null, {
  				constructor: function (args) { lang.mixin(this, args); }
  			});
  			declare('tests.parser.ShieldedContainer', null, {
  				constructor: function (args) { lang.mixin(this, args); },
  
  				// flag to tell parser not to instantiate nodes inside of me
  				stopParser: true
  			});
  
  			declare('tests.parser.HTML5Props', null, {
  				constructor: function (args) { lang.mixin(this, args); },
  				simple: false,
  				a: 2,
  				b: null,
  				c: null,
  				d: null,
  				e: null,
  				f: null,
  				afn: function () {
  					return this.a * 2;
  				}
  			});
  
  			// not on .prototype:
  			tests.parser.HTML5Props._aDefaultObj = {
  				a: 1,
  				b: 2,
  				simple: true
  			};
  
  			declare('tests.parser.HTML5withMethod', null, {
  				constructor: function (args) { lang.mixin(this, args); },
  				baseValue: 10,
  				someMethod: function () {
  					return this.baseValue;
  				},
  				diffMethod: function () {
  					this._ran = true;
  				}
  			});
  
  			declare('tests.parser.StatefulClass', [Evented, Stateful], {
  				strProp1: '',
  				objProp1: {},
  				boolProp1: false,
  				prototypeOnclick: false,
  				onclick: function () { this.prototypeOnclick = true; }
  			});
  
  			declare('tests.parser.MethodClass', null, {
  				method1ran: false,
  				method1after: false,
  				method2ran: false,
  				method2before: false,
  				method2after: false,
  				method3result: '',
  				method4ran: false,
  				method4after: false,
  				method1: function () { this.method1ran = true; },
  				method2: function () { this.method2ran = true; },
  				method3: function (result) { this.method3result = result; },
  				method4: function () { this.method4ran = true; }
  			});
  
  			declare('tests.parser.ClassForMixins', null, {
  				classDone: true
  			});
  
  			declare('tests.parser.Mixin1', null, {
  				mixin1Done: true
  			});
  
  			declare('tests.parser.Mixin2', null, {
  				mixin2Done: true
  			});
  
  			declare('tests.resources.AMDWidget', null, {
  				constructor: function (args) {
  					this.params = args;
  				}
  			});
  
  			declare('tests.resources.AMDWidget2', null, {
  				constructor: function (args) {
  					this.params = args;
  				},
  
  				method1: function (value) {
  					value++;
  					return value;
  				}
  			});
  
  			declare('tests.resources.AMDWidget3', null, {
  				constructor: function (args) {
  					this.params = args;
  				}
  			});
  
  			window.deepTestProp = {
  				blah: {
  					thinger: 1
  				}
  			};
  
  			tests.parser.FormClass = declare(tests.parser.Widget, {
  				encType: ''
  			});
  
  			window.foo = function () {
  				this.fooCalled = true;
  			};
  
  			container = domConstruct.place(util.fixScope(template), document.body);
  			var el = id ? dom.byId(id) : null,
  				ret = parser.parse(el);
  
  			return shouldReturn && ret;
  		};
  	}
  
  	function teardown() {
  		domConstruct.destroy(container);
  		container = null;
  		window.foo = undefined;
  	}
  
  	registerSuite({
  		name: 'dojo/parser basic tests',
  		setup: setup('main', true),
  
  		teardown: teardown,
  
  		/* global obj */
  		'data-dojo-id': function () {
  			assert.isObject(obj);
  		},
  
  		/* global obj3 */
  		'JsId': function () {
  			assert.isObject(obj3);
  		},
  
  		'string property': function () {
  			assert.isString(obj.strProp1);
  			assert.equal(obj.strProp1, 'text');
  		},
  
  		'int property': function () {
  			assert.isNumber(obj.intProp);
  			assert.equal(obj.intProp, 5);
  		},
  
  		'array property': function () {
  			assert.lengthOf(obj.arrProp, 3);
  			assert.lengthOf(obj.arrProp[1], 3);
  			assert.equal(obj.arrProp[1], 'bar');
  		},
  
  		'boolean property': function () {
  			// boolProp1
  			assert.isBoolean(obj.boolProp1);
  			assert.isTrue(obj.boolProp1);
  
  			// boolProp2
  			assert.isBoolean(obj.boolProp2);
  			assert.isFalse(obj.boolProp2);
  
  			// boolProp3 not specified (prototype says false)
  			assert.isBoolean(obj.boolProp3);
  			// assert.isFalse(obj.boolprop3);
  
  			// boolProp4 not specified (prototype says true)
  			// assert.isBoolean(obj.boolProp4);
  			// assert.isTrue(obj.boolProp4);
  		},
  
  		'date property': function () {
  			assert.equal(dstamp.toISOString(obj.dateProp1, { selector: 'date' }), '2006-01-01');
  			// dateProp2='', should map to NaN (a blank value on DateTextBox)
  			assert.ok(isNaN(obj.dateProp2));
  
  			// dateProp3='now', should map to current date
  			assert.equal(dstamp.toISOString(obj.dateProp3, { selector: 'date' }),
  				dstamp.toISOString(new Date(), { selector: 'date' }));
  		},
  
  		'unwanted params': function () {
  			// Make sure that parser doesn't pass any unwanted parameters to
  			// widget constructor, especially 'toString' or 'constructor'.
  			// Make exception for dir/lang which parser gleans from document itself.
  			for (var param in obj.params) {
  				assert.ok(array.indexOf(
  					[
  						'strProp1', 'strProp2',
  						'intProp',
  						'arrProp', 'arrProp2',
  						'boolProp1', 'boolProp2',
  						'dateProp1', 'dateProp2', 'dateProp3',
  						'funcProp2', 'funcProp3',
  						'preamble',
  						'callInc1', 'callInc2', 'dir', 'lang', 'textDir'
  					],
  				param) >= 0, param + ' should not be in the parameters passed to the widget constructor');
  			}
  		},
  
  		'disabled flag': function () {
  			/* global disabledObj */
  			assert.isBoolean(disabledObj.disabled);
  			assert.isTrue(disabledObj.disabled);
  			assert.isFalse(disabledObj.checked);
  		},
  
  		'checked flag': function () {
  			/* global checkedObj */
  			assert.isBoolean(checkedObj.checked);
  			assert.isFalse(checkedObj.disabled);
  			assert.isTrue(checkedObj.checked);
  		},
  
  		'function property': function () {
  			// make sure that unspecified functions (even with common names)
  			// don't get overridden (bug #3074)
  			obj.onclick();
  			assert.isTrue(obj.prototypeOnclick, 'prototypeOnClick');
  
  			// funcProp2='foo'
  			obj.funcProp2();
  			assert.isTrue(obj.fooCalled, 'fooCalled');
  
  			// funcProp3='this.func3Called=true;'
  			obj.funcProp3();
  			assert.isTrue(obj.func3Called, 'func3Called');
  		},
  
  		'connect': function () {
  			obj.callInc();
  			assert.equal(obj.callCount, 2);
  		},
  
  		'function assignment': function () {
  			obj.callInc2();
  			assert.equal(obj.callCount2, 1);
  		},
  
  		'subnode parse': function () {
  			assert.isFalse(lang.exists('obj2'), 'exists before parse');
  			var toParse = dom.byId('toParse');
  			parser.parse(toParse.parentNode);
  			assert.isTrue(lang.exists('obj2'), 'exists after parse');
  			assert.equal(obj.declaredClass, 'tests.parser.Class1');
  		},
  
  		'markup factory': function () {
  			assert.isTrue(lang.exists('obj3'), 'obj3 exists');
  			assert.isTrue(obj3.fromMarkup);
  		},
  
  		/* global obj4 */
  		'markup factory class': function () {
  			assert.isTrue(lang.exists('obj4'), 'obj4 exists');
  			assert.equal(obj4.classCtor, tests.parser.Class3);
  			assert.instanceOf(obj4, tests.parser.Class3);
  			assert.instanceOf(obj4, tests.parser.Class2);
  		},
  
  		'no start': function () {
  			var started = false;
  			declare('SampleThinger', null, {
  				startup: function () {
  					started = true;
  				}
  			});
  
  			domConstruct.create('div', {
  				dojoType: 'SampleThinger'
  			}, 'parsertest');
  
  			parser.parse('parsertest', { noStart: true });
  			assert.isFalse(started, 'first started check');
  
  			domConstruct.empty('parsertest');
  
  			started = false;
  
  			domConstruct.create('div', {
  				dojoType: 'SampleThinger'
  			}, 'parsertest');
  
  			parser.parse('parsertest', { noStart: true, rootNode: 'parserTest' });
  			assert.isFalse(started, 'second started check');
  		},
  
  		// test the varios iterations of parser test
  		// TODO: the following test doesn't acutally test anything because
  		// the parser is calling dojo/query's function instead of dojo.query
  		// This also causes other tests to fail because the aspect call replaces
  		// dojo.query which has functions on it (like dojo.query.matches)
  		/*'root test': function () {
  			var tmp = aspect.after(dojo, 'query', function (sel, root) {
  				assert.equal(root, 'parsertest2');
  			});
  
  			parser.parse('parsertest2');
  			parser.parse({ rootNode: 'parsertest2' });
  			parser.parse('parsertest2', { noStart: true });
  			tmp.remove();
  		},*/
  
  		// Test that when BorderContainer etc. extends _Widget,
  		// parser is aware of the new parameters added (to _Widget
  		// and all of it's subclasses)
  		'cache refresh': function () {
  			// Add new node to be parsed, referencing a widget that the parser has already
  			// dealt with (and thus cached)
  			var wrapper = domConstruct.place(
  				util.fixScope('<div><div ${dojo}Type="tests.parser.Class3" newParam=12345>hi</div></div>'),
  				win.body(), 'last');
  
  			try {
  				// Modify Class3's superclass widget to have new parameter (thus Class3 inherits it)
  				lang.extend(tests.parser.Class2, {
  					newParam: 0
  				});
  
  				// Run the parser and see if it reads in newParam
  				var widgets = parser.parse({ rootNode: wrapper });
  				assert.lengthOf(widgets, 1);
  				assert.equal(widgets[0].params.newparam || widgets[0].params.newParam, 12345);
  			}
  			finally {
  				domConstruct.destroy(wrapper);
  			}
  		},
  
  		// Test that parser recurses correctly, except when there's a stopParser flag not to
  		/* global container1, contained1, container2, contained2 */
  		'recurse': function () {
  			assert.isDefined(container1, 'normal container created');
  			assert.isDefined(container1.incr, 'script tag works too');
  			assert.isDefined(contained1, 'child widget also created');
  			assert.isDefined(contained2, 'child widget 2 also created');
  
  			assert.isDefined(container2, 'shielded container created');
  			assert.isDefined(container2.incr, 'script tag works too');
  			assert.isUndefined(window.contained3, 'child widget not created');
  			assert.isUndefined(window.contained4, 'child widget 2 not created');
  		},
  
  		/* global html5simple, html5simple2 */
  		'simple HTML5': function () {
  			assert.isObject(html5simple, 'data-dojo-id export');
  			assert.isObject(html5simple2, 'data-dojo-id export');
  
  			assert.isTrue(html5simple.simple, 'default respecified in props=""');
  			assert.isFalse(html5simple2.simple, 'default overridden by props=""');
  
  			// test data-dojo-props='simple:false, a:1, b:'two', c:[1,2,3], d:function(){ return this; }, e:{ f:'g' }'
  			var it = html5simple2;
  			assert.equal(it.a, 1, 'number in param');
  			assert.equal(it.b, 'two', 'string in param');
  			assert.isArray(it.c, 'array in param');
  			assert.lengthOf(it.c, 3, 'array sanity');
  			assert.equal(it.e.f, 'g', 'nested object with string');
  
  			// test the function
  			assert.equal(it.d(), it, 'simple \'return this\' function');
  		},
  
  		/* global html5simple3 */
  		'HTML5 inherited': function () {
  			assert.isObject(html5simple3);
  			var val = html5simple3.afn();
  			assert.equal(val, html5simple3.a * 2, 'afn() overrides default but calls inherited');
  		},
  
  		/* global htmldojomethod */
  		'HTML5 with method': function () {
  			// testing data-dojo-event and data-dojo-args support for dojo/method and dojo/connect
  			assert.isObject(htmldojomethod);
  			assert.isTrue(htmldojomethod._methodRan, 'plain dojo/method ran');
  
  			var x = htmldojomethod.someMethod(2, 2);
  			assert.equal(x, 14, 'overridden dojo/method');
  
  			htmldojomethod.diffMethod(2);
  			assert.isTrue(htmldojomethod._ran, 'ensures original was called first');
  			assert.equal(htmldojomethod._fromvalue, 2, 'ensures connected was executed in scope');
  		},
  
  		/* global objOnWatch */
  		'test watch': function () {
  			// testing script-type dojo/watch and dojo/on
  			assert.isObject(objOnWatch);
  			objOnWatch.set('strProp1', 'newValue1');
  			assert.equal(objOnWatch.arrProp.newValue, 'newValue1', 'ensures watch executed');
  
  			objOnWatch.onclick();
  			assert.isTrue(objOnWatch.prototypeOnclick, 'ensures original was called');
  			assert.isTrue(objOnWatch.boolProp1, 'ensure on executed in scope');
  		},
  
  		/* global on_form */
  		'on': function () {
  			/*jshint camelcase:false*/
  			// testing script-type dojo/on, when script comes after another element
  			parser.parse('on');
  			assert.property(window, 'on_form', 'widget created');
  			on_form.emit('click');
  			assert.isTrue(on_form.clicked, 'on callback fired');
  		},
  
  
  		/* global objAspect */
  		'aspect': function () {
  			// testing script-type dojo/aspect
  			assert.isObject(objAspect);
  			assert.isFalse(objAspect.method1ran, 'ensures method unfired');
  			assert.isFalse(objAspect.method2ran, 'ensures method unfired');
  			assert.equal(objAspect.method3result, '', 'ensures method unfired');
  			assert.isFalse(objAspect.method4ran, 'ensures method unfired');
  
  			objAspect.method1();
  			objAspect.method2();
  			objAspect.method3('something');
  			objAspect.method4();
  
  			assert.isTrue(objAspect.method1ran, 'method fired');
  			assert.isTrue(objAspect.method1after, 'after advice fired');
  			assert.isTrue(objAspect.method2ran, 'method fired');
  			assert.isTrue(objAspect.method2before, 'around before advice fired');
  			assert.isTrue(objAspect.method2after, 'around after advice fired');
  			assert.equal(objAspect.method3result, 'before', 'before argument passed');
  			assert.isTrue(objAspect.method4ran, 'method fired');
  			assert.isTrue(objAspect.method4after, 'after advice fired');
  		},
  
  		/* global objAMDWidget */
  		'mid': function () {
  			// testing specifying data-dojo-type as mid
  			assert.isObject(objAMDWidget);
  			assert.equal(objAMDWidget.params.value, 'Value1', 'ensure object was properly parsed using MID');
  		}
  	});
  
  	registerSuite({
  		name: 'bidi',
  
  		setup: setup('main'),
  
  		teardown: teardown,
  
  		// Test that dir=rtl or dir=ltr setting trickles down from root node
  		/* global setRtl, inheritRtl, inheritRtl2, inheritLtr, setLtr */
  		'dir attribute': function () {
  			parser.parse('dirSection1');
  			parser.parse('dirSection2');
  			assert.equal(setRtl.dir, 'rtl', 'direct setting of dir=rtl works');
  			assert.equal(inheritRtl.dir, 'rtl', 'inherited rtl works');
  			assert.equal(inheritLtr.dir, 'ltr', 'inherited ltr works (closest ancestor wins)');
  			assert.equal(inheritRtl2.dir, 'rtl', 'inherited rtl works, from grandparent');
  			assert.equal(setLtr.dir, 'ltr', 'direct setting of dir=ltr overrides inherited RTL');
  		},
  
  		/* global noLang, inheritedLang, specifiedLang */
  		'lang attribute': function () {
  			parser.parse('langSection');
  			assert.notProperty(noLang.params, 'lang', 'no lang');
  			assert.equal(inheritedLang.lang, 'it_it', 'inherited lang works');
  			assert.equal(specifiedLang.lang, 'en_us', 'direct setting of lang overrides inherited');
  		},
  
  		/* global noTextdir, inheritedTextdir, specifiedTextdir */
  		'textDir attribute': function () {
  			parser.parse('textDirSection');
  			assert.notProperty(noTextdir.params, 'textDir', 'no textdir');
  			assert.equal(inheritedTextdir.textDir, 'rtl', 'inherited textdir works');
  			assert.equal(specifiedTextdir.textDir, 'ltr', 'direct setting of textdir overrides inherited');
  		},
  
  		'inheritance from HTML': function () {
  			// Test that calling parser.parse(nodeX) will inherit dir/lang/etc. settings
  			// even from <html>
  
  			var textdirAttr = util.fixScope('data-${dojo}-textdir');
  			var attrs = { dir: 'rtl', lang: 'ja-jp' };
  			attrs[textdirAttr] = 'auto';
  			domAttr.set(win.doc.documentElement, attrs);
  			parser.parse('bidiInheritanceFromHtml');
  
  			/* global inheritedFromHtml */
  			assert.equal(inheritedFromHtml.params.dir, 'rtl', 'dir');
  			assert.equal(inheritedFromHtml.params.lang, 'ja-jp', 'lang');
  			assert.equal(inheritedFromHtml.params.textDir, 'auto', 'textDir');
  
  			// teardown
  			array.forEach(['dir', 'lang', textdirAttr], function (attr) {
  				win.doc.documentElement.removeAttribute(attr);
  			});
  		}
  	});
  
  	registerSuite({
  		name: 'IE Attribute Detection',
  
  		setup: setup('main'),
  
  		teardown: teardown,
  
  		'input1': function () {
  			var widgets = parser.instantiate([dom.byId('ieInput1')]);
  			var params = widgets[0].params;
  
  			assert.equal(params.type, 'checkbox', 'type');
  			assert.isTrue(params.disabled, 'disabled');
  			assert.isTrue(params.checked, 'checked');
  			assert.isTrue(params.readonly, 'readonly');
  			assert.equal(params.foo, 'bar', 'foo');
  			assert.equal(params.bar, 'zaz', 'bar');
  			assert.equal(params.bob, 'escaped\"dq', 'bob');
  			assert.equal(params.frank, 'escaped\'sq', 'frank');
  			//assert.isFalse('value' in params, 'value not specified');	// fails in IE8, thinks value=='on'
  		},
  
  		'input2': function () {
  			var widgets = parser.instantiate([dom.byId('ieInput2')]);
  			var params = widgets[0].params;
  
  			assert.notProperty(params, 'type', 'type');
  			assert.notProperty(params, 'name', 'name');
  			assert.notProperty(params, 'value', 'value');
  			assert.notProperty(params, 'data-dojo-type', 'data-dojo-type');
  			assert.notProperty(params, 'data-dojo-props', 'data-dojo-props');
  			assert.equal(params.foo, 'hi', 'foo');
  			assert.notProperty(params, 'value', 'value not specified');
  		},
  
  		'input3': function () {
  			var widgets = parser.instantiate([dom.byId('ieInput3')]);
  			var params = widgets[0].params;
  
  			assert.equal(params.type, 'password', 'type');
  			assert.equal(params.name, 'test', 'name');
  			assert.equal(params.value, '123', 'value');
  			assert.equal(params['class'], 'myClass', 'class');
  			assert.equal(params.style.replace(/[ ;]/g, '').toLowerCase(), 'display:block', 'style');
  			assert.equal(params.tabIndex, '3', 'tabIndex');
  		},
  
  		'textarea': function () {
  			var widgets = parser.instantiate([dom.byId('ieTextarea')]);
  			var params = widgets[0].params;
  
  			assert.equal(params.value, 'attrVal', 'value');
  		},
  
  		'button1': function () {
  			var widgets = parser.instantiate([dom.byId('ieButton1')]);
  			var params = widgets[0].params;
  
  			assert.isTrue(params.checked, 'checked');
  			assert.equal(params.value, 'button1val', 'value');
  		},
  
  		'button2': function () {
  			var widgets = parser.instantiate([dom.byId('ieButton2')]);
  			var params = widgets[0].params;
  			assert.notProperty(params, 'checked', 'checked');
  			assert.notProperty(params, 'value', 'value');
  		},
  
  		'button3': function () {
  			var widgets = parser.instantiate([dom.byId('ieButton3')]);
  			var params = widgets[0].params;
  			assert.isTrue(params.checked, 'checked');
  		},
  
  		'button4': function () {
  			var widgets = parser.instantiate([dom.byId('ieButton4')]);
  			var params = widgets[0].params;
  			assert.notProperty(params, 'checked');
  		},
  
  		'form1': function () {
  			var widgets = parser.instantiate([dom.byId('ieForm1')]);
  			var params = widgets[0].params;
  
  			assert.equal(params.encType, 'foo', 'encType is specified');
  		},
  
  		'form2': function () {
  			var widgets = parser.instantiate([dom.byId('ieForm2')]);
  			var params = widgets[0].params;
  
  			assert.notProperty(params, 'encType', 'encType not specified');
  		},
  
  		'li': function () {
  			var widgets = parser.instantiate([dom.byId('li')]);
  			var params = widgets[0].params;
  			assert.equal(params.value, 'home');
  		}
  	});
  
  	registerSuite({
  		name: 'mixed attribute specification',
  
  		setup: setup('main'),
  
  		teardown: teardown,
  
  		/* global mixedObj */
  		'mixed': function () {
  			parser.parse(dom.byId('mixedContainer'));
  			assert.isObject(mixedObj, 'widget created');
  			assert.equal(mixedObj.value, 'mixedValue', 'native attribute');
  			assert.equal(mixedObj.custom1, 999, 'data-dojo-props attribute');
  			assert.equal(mixedObj.title, 'custom title', 'data-dojo-props overrides native');
  		}
  	});
  
  	registerSuite({
  		name: 'functions',
  
  		setup: setup('main'),
  
  		teardown: teardown,
  
  		'onclick': function () {
  			declare('tests.parser.Button', null, {
  				onClick: function () {
  					console.log('prototype click');
  				},
  				constructor: function (args, node) {
  					lang.mixin(this, args);
  					this.domNode = node;
  					aspect.after(this.domNode, 'onclick', lang.hitch(this, 'onClick'));
  				}
  			});
  
  			buttonClicked = function () {
  				console.log('markup click');
  			};	// markup says onClick='buttonClicked'
  
  			// Parse markup inside 'functions' div
  			parser.parse('functions');
  
  			// Should have created an instance called 'button' where button.onClick == buttonClicked
  			/* global button, buttonClicked */
  			assert.isObject(button, 'widget created');
  			assert.isFunction(button.onClick, 'created as function');
  			assert.isTrue(buttonClicked === button.onClick, 'points to specified function');
  		}
  	});
  
  	registerSuite({
  		name: 'parser.construct()',
  
  		setup: setup('main'),
  
  		teardown: teardown,
  
  		/* global objC1, objC2 */
  		'construct1': function () {
  			// var nodes = [dom.byId('objC1'), dom.byId('objC2')];
  
  			parser.construct(tests.parser.Class1, dom.byId('objC1'));
  			assert.isObject(objC1, 'widget 1 created');
  			assert.equal(objC1.intProp, 5, 'objC1.intProp');
  
  			parser.construct(tests.parser.Class1, dom.byId('objC2'));
  			assert.isObject(objC2, 'widget 2 created');
  			assert.equal(objC2.intProp, 5, 'objC2.intProp');
  		}
  	});
  
  	registerSuite({
  		name: 'data-dojo-mixins support',
  
  		setup: setup('mixins', true),
  
  		teardown: teardown,
  
  		'mixins': function () {
  			/* global resultMixins1, resultMixins2 */
  			assert.ok(resultMixins1, 'object using data-dojo-mixins created from an already parsed type');
  			assert.isTrue(resultMixins1.mixin1Done, 'mixin1 correctly mixed in');
  			assert.isTrue(resultMixins1.mixin2Done, 'mixin2 correctly mixed in');
  			assert.isTrue(resultMixins1.amdMixinDone, 'amd mixin correctly mixed in');
  			assert.ok(resultMixins2, 'object using data-dojo-mixins created from a non parsed type');
  			assert.isTrue(resultMixins2.classDone, 'class correctly created');
  			assert.isTrue(resultMixins2.mixin1Done, 'mixin1 correctly mixed in');
  			assert.isTrue(resultMixins2.mixin2Done, 'mixin2 correctly mixed in');
  			assert.isTrue(resultMixins2.amdMixinDone, 'amd mixin correctly mixed in');
  
  			/* global resultNonDojoMixin */
  			assert.isTrue(resultNonDojoMixin.expectedClass, 'correct class is returned for composeJS mixin');
  			assert.equal(resultNonDojoMixin.params.length, 2, 'correct # of params were passed to compose JS');
  			assert.equal(resultNonDojoMixin.params[0], tests.parser.Mixin1, 'correct param 1');
  			assert.equal(resultNonDojoMixin.params[1], tests.parser.Mixin2, 'correct param 2');
  		}
  	});
  
  	registerSuite({
  		name: 'behavorial',
  
  		setup: setup('main'),
  
  		teardown: teardown,
  
  		'doubleConnect': function () {
  			// Class used in 'behavioral' <div>
  			/* global Behavioral1:true */
  			Behavioral1 = declare(null, {
  				constructor: function (params, node) {
  					on(node, 'click', lang.hitch(this, 'onClick'));
  					if (typeof params.onClick !== 'function') {
  						throw new Error('onClick not passed to constructor');
  					}
  					lang.mixin(this, params);
  				},
  				onClick: function () {
  					console.log('original onnClick handler');
  				},
  				foo: ''
  			});
  
  			parser.parse('behavioral');
  
  			// Setup global accessed by Behavioral1.onclick handler
  			behavioralClickCounter = 0;
  
  			// Trigger click event, and make sure that handler was only called once.
  			on.emit(dom.byId('bh1'), 'click', {bubbles: true, cancelable: true});
  
  			/* global behavioralClickCounter */
  			assert.equal(behavioralClickCounter, 1, 'one click event processed');
  
  			assert.equal(dom.byId('bh1').getAttribute('foo'), 'bar', 'foo attribute not removed from widget DOMNode');
  		}
  	});
  
  	registerSuite({
  		name: 'script type=dojo/require support',
  
  		setup: setup('main'),
  
  		teardown: teardown,
  
  		'declarativeRequire': function () {
  			var td = this.async();
  
  			parser.parse('declarativeRequire').then(td.callback(function () {
  				/* global dr1, dr2, dr3, dr4, dr5 */
  				assert.isObject(dr1, 'object using MID mapped to return var');
  				assert.equal(dr1.params.foo, 'bar', 'parameters set on instantiation');
  				assert.isObject(dr2, 'object using MID mapped to return var');
  				assert.equal(dr2.params.foo, 'bar', 'parameters set on instantiation');
  				assert.isObject(dr3, 'object using fully required');
  				assert.equal(dr3.params.foo, 'bar', 'parameters set on instantiation');
  				assert.equal(dr4.params.foo, 2, 'module loaded and executed');
  				assert.equal(dr5.method1(1), 3, 'declarative script has access to parser scope');
  			}));
  		},
  
  		'contextRequire': function () {
  			var td = this.async();
  
  			parser.parse('contextRequire', {
  				contextRequire: require
  			}).then(td.callback(function () {
  				/* global cr1, cr2, cr3, cr4 */
  				assert.isObject(cr1, 'object using relative MID mapped to return var');
  				assert.equal(cr1.params.foo, 'bar', 'parameters set on instantiation');
  				assert.isObject(cr2, 'object using relative MID mapped to return var');
  				assert.equal(cr2.params.foo, 'bar', 'parameters set on instantiation');
  				assert.isObject(cr3, 'object using relative MID mapped to return var');
  				assert.equal(cr3.params.foo, 'bar', 'parameters set on instantiation');
  				assert.isObject(cr4, 'object using relative MID mapped to return var');
  				assert.equal(cr4.params.foo, 'bar', 'parameters set on instantiation');
  			}));
  		}
  	});
  
  	registerSuite({
  		name: 'promise error handling support',
  
  		setup: setup('main'),
  
  		teardown: teardown,
  
  		'asyncError': function () {
  			var td = this.async();
  
  			parser.parse('errorHandling').then(td.rejectOnError(function () {
  				throw new Error('shouldn\'t get here');
  			}), td.callback(function (e) {
  				assert.equal(typeof e, 'object', 'error object returned');
  			}));
  		},
  
  		'missingCtor': function () {
  			var td = this.async();
  
  			parser.parse('missingCtor').then(td.rejectOnError(function () {
  				throw new Error('shouldn\'t get here');
  			}), td.callback(function (e) {
  				assert.equal(typeof e, 'object', 'error object returned');
  				assert.equal(e.toString(),
  					'Error: Unable to resolve constructor for: \'some.type\'', 'proper error value returned');
  			}));
  		}
  	});
  });
  
  