diff --git a/.gitignore b/.gitignore
index 31226a4..c7ed7ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
 node_modules/
-temp/
+test/temp/
diff --git a/demo_frame.js b/demo_frame.js
index d58f1d3..4049471 100644
--- a/demo_frame.js
+++ b/demo_frame.js
@@ -45,9 +45,11 @@ module.exports = function(node){
 			var dataForHtml = node.querySelector("[data-for=html] > pre");
 			dataForHtml.innerHTML = prettyify(html);
 
-			var dataForJS = node.querySelector("[data-for=js] > pre");
-			dataForJS.innerHTML = prettyify(js.replace(/\t/g,"  "));
-			show(node.querySelector("[data-tab=js]"));
+			if (js) {
+				var dataForJS = node.querySelector("[data-for=js] > pre");
+				dataForJS.innerHTML = prettyify(js.replace(/\t/g,"  "));
+				show(node.querySelector("[data-tab=js]"));
+			}
 
 			tabs();
 	}
@@ -86,11 +88,11 @@ module.exports = function(node){
 					}
 				}
 			}
-			return source.trim();
+			return (source ? source.trim() : '');
 	}
 
 	function show(el) {
-		el.style.display = "block";
+		el.style.display = "";
 	}
 
 	function hide(el) {
@@ -101,7 +103,7 @@ module.exports = function(node){
 		node.querySelector("ul").addEventListener("click", function(ev){
 			var el = ev.target;
 			if(el.className === "tab") {
-				toggle(el.dataset.tab);
+				toggle(el.dataset ? el.dataset.tab : el.getAttribute("data-tab"));
 			}
 		});
 		toggle("demo");
diff --git a/package.json b/package.json
index c562a18..284b892 100644
--- a/package.json
+++ b/package.json
@@ -2,37 +2,42 @@
   "name": "bit-docs-tag-demo",
   "version": "0.3.0",
   "description": "@demo tag for bit-docs",
-  "main": "demo.js",
-  "scripts": {
-    "test": "mocha test.js --reporter spec",
-    "postversion": "git push --tags && git push",
-    "preversion": "npm test",
-    "release:pre": "npm version prerelease && npm publish",
-    "release:patch": "npm version patch && npm publish",
-    "release:minor": "npm version minor && npm publish",
-    "release:major": "npm version major && npm publish"
-  },
-  "repository": {
-    "type": "git",
-    "url": "git+ssh://git@github.com/bit-docs/bit-docs-tag-demo.git"
-  },
   "keywords": [
     "bit-docs"
   ],
-  "author": "Bitovi",
-  "license": "MIT",
+  "homepage": "https://github.com/bit-docs/bit-docs-tag-demo#readme",
   "bugs": {
     "url": "https://github.com/bit-docs/bit-docs-tag-demo/issues"
   },
-  "homepage": "https://github.com/bit-docs/bit-docs-tag-demo#readme",
-  "dependencies": {
-    "bit-docs-process-tags": "^0.0.3",
-    "bit-docs-type-annotate": "^0.0.1"
+  "license": "MIT",
+  "author": "Bitovi",
+  "main": "demo.js",
+  "repository": {
+    "type": "git",
+    "url": "git+ssh://git@github.com/bit-docs/bit-docs-tag-demo.git"
+  },
+  "scripts": {
+    "release:major": "npm version major && npm publish",
+    "release:minor": "npm version minor && npm publish",
+    "release:patch": "npm version patch && npm publish",
+    "release:pre": "npm version prerelease && npm publish",
+    "test": "mocha test.js --reporter spec",
+    "preversion": "npm test",
+    "postversion": "git push --tags && git push"
   },
   "devDependencies": {
-    "bit-docs-generate-html": "^0.2.0",
-    "connect": "^2.14.4",
-    "mocha": "^2.5.3",
-    "zombie": "^4.2.1"
+    "bit-docs-generate-html": "0.5.0-pre.4",
+    "can-stache": "3.0.20",
+    "express": "^4.15.2",
+    "mocha": "^3.2.0",
+    "rimraf": "^2.6.1",
+    "steal": "^1.4.6",
+    "steal-less": "^1.2.0",
+    "zombie": "^5.0.5"
+  },
+  "steal": {
+    "plugins": [
+      "steal-less"
+    ]
   }
 }
diff --git a/test.js b/test.js
index 85d729b..0ddcf02 100644
--- a/test.js
+++ b/test.js
@@ -1,93 +1,310 @@
-var assert = require("assert");
-var generate = require("bit-docs-generate-html/generate");
-var path = require("path");
-
-var Browser = require("zombie"),
-	connect = require("connect");
-
-
-var find = function(browser, property, callback, done){
-	var start = new Date();
-	var check = function(){
-		if(browser.window && browser.window[property]) {
-			callback(browser.window[property]);
-		} else if(new Date() - start < 2000){
-			setTimeout(check, 20);
-		} else {
-			done("failed to find "+property);
-		}
-	};
-	check();
-};
-var waitFor = function(browser, checker, callback, done){
-	var start = new Date();
-	var check = function(){
-		if(checker(browser.window)) {
-			callback(browser.window);
-		} else if(new Date() - start < 2000){
-			setTimeout(check, 20);
-		} else {
-			done(new Error("checker was never true"));
-		}
-	};
-	check();
-};
+var Browser = require('zombie');
+var assert = require('assert');
+var express = require('express');
+var generate = require('bit-docs-generate-html/generate');
+var path = require('path');
+var rmrf = require('rimraf');
 
+Browser.localhost('*.example.com', 3003);
 
-var open = function(url, callback, done){
-	var server = connect().use(connect.static(path.join(__dirname))).listen(8081);
+/*
+ * Debug options:
+ *	npm --debug test
+ *	npm --devBuild test
+ *	npm --skipGenerate test
+ *	npm --debug --devBuild test
+ *	npm --debug --skipGenerate test
+ */
+
+describe('bit-docs-tag-demo', function () {
 	var browser = new Browser();
-	browser.visit("http://localhost:8081/"+url)
-		.then(function(){
-			callback(browser, function(){
-				server.close();
-			});
-		}).catch(function(e){
-			server.close();
-			done(e)
-		});
-};
+	var server = express();
+	var temp = path.join(__dirname, 'test', 'temp');
 
-describe("bit-docs-tag-demo", function(){
-	it("basics works", function(done){
-		this.timeout(60000);
+	before(function () {
+		if (!!process.env.npm_config_debug) {
+			browser.debug();
+		}
+
+		return new Promise(function (resolve, reject) {
+			server = server.use('/', express.static(__dirname)).listen(3003, resolve);
+			server.on('error', reject);
+		});
+	});
 
-		var docMap = Promise.resolve({
-			index: {
-				name: "index",
-				demo: "path/to/demo.html",
-				body: "<div class='demo_wrapper' data-demo-src='test/basics/demo.html'></div>\n"
+	describe('temp directory', function () {
+		before(function (done) {
+			if (!!process.env.npm_config_skipGenerate) {
+				this.skip();
 			}
+
+			rmrf(temp, done);
 		});
 
-		generate(docMap, {
-			html: {
-				dependencies: {
-					"bit-docs-tag-demo": __dirname
+		it('is generated', function () {
+			this.timeout(60000);
+
+			function demo_wrapper(path) {
+				return '<div class="demo_wrapper" data-demo-src="demos/' + path + '.html"></div>' + "\n"
+			}
+
+			function all_demos() {
+				return [
+					'demo-with-ids',
+					'demo-without-ids',
+					'demo-without-js',
+					'demo-complex'
+				].map(function (demo) {
+					return '<h2>' + demo + '</h2>' + demo_wrapper(demo);
+				}).join('<br>');
+			}
+
+			var docMap = Promise.resolve({
+				withIds: {
+					name: 'withIds',
+					body: demo_wrapper('demo-with-ids')
+				}, withoutIds: {
+					name: 'withoutIds',
+					body: demo_wrapper('demo-without-ids')
+				}, withoutJs: {
+					name: 'withoutJs',
+					body: demo_wrapper('demo-without-js')
+				}, complex: {
+					name: 'complex',
+					body: demo_wrapper('demo-complex')
+				}, index: {
+					name: 'index',
+					body: all_demos()
 				}
-			},
-			dest: path.join(__dirname, "temp"),
-			parent: "index",
-			forceBuild: true,
-			debug: true,
-			minifyBuild: false
-		}).then(function(){
-
-			open("temp/index.html",function(browser, close){
-				waitFor(browser, function(window){
-					return window.document.getElementsByClassName("demo").length;
-				}, function(){
-				var doc = browser.window.document;
-				var tabs = doc.getElementsByClassName("tab");
-
-				assert.equal(tabs.length, 3, "there are 3 tabs");
-
-				// TODO better testing, click on stuff
-
-				close();
-				done();
+			});
+
+			var siteConfig = {
+				html: {
+					dependencies: {
+						'bit-docs-tag-demo': 'file://' + __dirname
+					}
+				},
+				dest: temp,
+				debug: !!process.env.npm_config_debug,
+				devBuild: !!process.env.npm_config_devBuild,
+				parent: 'index',
+				forceBuild: true,
+				minifyBuild: false
+			};
+
+			return generate(docMap, siteConfig);
+		});
+	});
+
+	describe('demo widget', function () {
+		function basicsWork() {
+			it('exists on page', function () {
+				browser.assert.success();
+				browser.assert.global('PACKAGES');
+				browser.assert.element('.demo_wrapper', 'wrapper exists');
+				browser.assert.element('.demo_wrapper .demo', 'injected into wrapper');
+			});
+
+			describe('tabs and contents', function () {
+				it('has three', function () {
+					browser.assert.elements('.tab', 3, 'there are three tabs');
+					browser.assert.elements('.tab-content', 3, 'there are three tab contents');
+				});
+
+				it('only one active', function () {
+					browser.assert.element('.tab.active', 'only one tab is active');
+					browser.assert.element('.tab-content:not([style*="none"])', 'only one tab content is visible');
 				});
-			},done);
+
+				it('defaults to demo', function () {
+					browser.assert.text('.tab.active', 'Demo', '"Demo" is active tab text');
+					browser.assert.attribute('.tab.active', 'data-tab', 'demo', 'demo is active data-tab');
+					browser.assert.style('[data-for="demo"]', 'display', '', 'demo tab content is visible');
+				});
+			});
+
+			describe('clicking HTML tab', function () {
+				before(function () {
+					return browser.click('[data-tab="html"]');
+				});
+
+				it('changes tab and content', function () {
+					browser.assert.attribute('.tab.active', 'data-tab', 'html', 'html is active data-tab');
+					browser.assert.style('[data-for="html"]', 'display', '', 'html tab content is visible');
+				});
+			});
+		}
+
+		function iframeAssert(path, regex) {
+			describe('iframe (' + path + '.html)', function () {
+				var iframe;
+				var iframeDocument;
+
+				before(function () {
+					iframe = browser.query('iframe');
+					iframeDocument = iframe.contentWindow.document;
+				});
+
+				it('has correct url and parent', function () {
+					assert.equal(iframe.src, '../demos/' + path + '.html');
+					assert.equal(iframeDocument.URL, 'http://example.com/test/demos/' + path + '.html');
+					assert.equal(iframe.contentWindow.parent, browser.window.parent);
+				});
+
+				it('has correct content', function () {
+					assert(/Hello world/.test(iframeDocument.body.innerHTML));
+
+					if (regex instanceof RegExp) {
+						assert(regex.test(iframeDocument.body.innerHTML));
+					}
+				});
+			});
+		}
+
+		function dataforAssert(selector, strings) {
+			describe('data-for=' + selector, function () {
+				before(function () {
+					if (strings instanceof Array) {
+						strings = strings.join(' ');
+					}
+				});
+
+				it('has correct content', function () {
+					browser.assert.text('[data-for="' + selector + '"] pre', strings);
+				});
+			});
+		}
+
+		describe('with ids', function () {
+			before(function () {
+				return browser.visit('/test/temp/withIds.html');
+			});
+
+			basicsWork();
+
+			describe('Demo', function () {
+				iframeAssert('demo-with-ids', /it worked/);
+			});
+
+			describe('HTML', function () {
+				dataforAssert('html', '<b>Hello world!</b>');
+			});
+
+			describe('JS', function () {
+				dataforAssert('js', [
+					'var div = document.createElement("div");',
+					'div.textContent = "it worked!";',
+					'document.body.appendChild(div);'
+				]);
+			});
+		});
+
+		describe('without ids', function () {
+			before(function () {
+				return browser.visit('/test/temp/withoutIds.html');
+			});
+
+			basicsWork();
+
+			describe('Demo', function () {
+				iframeAssert('demo-without-ids', /it worked/);
+			});
+
+			describe('HTML', function () {
+				dataforAssert('html', [
+					'<div><b>Hello world!</b></div>',
+					'<div>it worked!</div>'
+				]);
+			});
+
+			describe('JS', function () {
+				dataforAssert('js', [
+					'var div = document.createElement("div");',
+					'div.textContent = "it worked!";',
+					'document.body.appendChild(div);'
+				]);
+			});
+		});
+
+		describe('without js', function () {
+			before(function () {
+				return browser.visit('/test/temp/withoutJs.html');
+			});
+
+			basicsWork();
+
+			describe('Demo', function () {
+				iframeAssert('demo-without-js');
+			});
+
+			describe('HTML', function () {
+				dataforAssert('html', '<b>Hello world!</b>');
+			});
+
+			describe('JS', function () {
+				// expect no content
+				dataforAssert('js', '');
+
+				it('tab is hidden', function () {
+					browser.assert.style('[data-tab="js"]', 'display', 'none', 'js tab is hidden');
+				});
+			});
+		});
+
+		describe('complex', function () {
+			this.timeout(4000);
+
+			before(function () {
+				return browser.visit('/test/temp/complex.html');
+			});
+
+			basicsWork();
+
+			describe('Demo', function () {
+				iframeAssert('demo-complex');
+			});
+
+			describe('HTML', function () {
+				dataforAssert('html', '<em>StealJS should load can-stache, which should appendChild here:</em>');
+			});
+
+			describe('JS', function () {
+				dataforAssert('js', /{{subject}}/);
+			});
+		});
+
+		describe('multiple instances', function () {
+			this.timeout(8000);
+
+			before(function () {
+				return browser.visit('/test/temp/index.html');
+			});
+
+			it('exist on page', function () {
+				browser.assert.success();
+				browser.assert.elements('.demo_wrapper', 4, 'four wrappers exists');
+				browser.assert.elements('.demo_wrapper .demo', 4, 'four injected into wrappers');
+			});
+
+			describe('clicking all HTML tabs', function () {
+				before(function () {
+					var htmlTabs = browser.queryAll('[data-tab="html"]');
+
+					return Promise.all(htmlTabs.map(function (el) {
+						browser.click(el);
+					}));
+				});
+
+				it('changes all tabs and contents', function () {
+					browser.assert.attribute('.tab.active', 'data-tab', 'html', 'html is active data-tab');
+					browser.assert.style('[data-for="html"]', 'display', '', 'html tab content is visible');
+				});
+			});
 		});
 	});
+
+	after(function () {
+		browser.destroy();
+		server.close();
+	});
 });
diff --git a/test/demos/demo-complex.html b/test/demos/demo-complex.html
new file mode 100644
index 0000000..e7db9a4
--- /dev/null
+++ b/test/demos/demo-complex.html
@@ -0,0 +1,9 @@
+<em>StealJS should load can-stache, which should appendChild here:</em>
+
+<script src="../../node_modules/steal/steal.js" main="@empty" id='demo-source'>
+import stache from "can-stache";
+var renderer = stache("<b>Hello {{subject}}!</b>");
+var fragment = renderer({subject: "world"});
+var el = document.getElementById("demo-html");
+document.body.appendChild(fragment);
+</script>
diff --git a/test/demos/demo-with-ids.html b/test/demos/demo-with-ids.html
new file mode 100644
index 0000000..6386786
--- /dev/null
+++ b/test/demos/demo-with-ids.html
@@ -0,0 +1,6 @@
+<div id="demo-html"><b>Hello world!</b></div>
+<script id="demo-source">
+	var div = document.createElement("div");
+	div.textContent = "it worked!";
+	document.body.appendChild(div);
+</script>
diff --git a/test/basics/demo.html b/test/demos/demo-without-ids.html
similarity index 80%
rename from test/basics/demo.html
rename to test/demos/demo-without-ids.html
index 9a19c8a..129bcb0 100644
--- a/test/basics/demo.html
+++ b/test/demos/demo-without-ids.html
@@ -1,4 +1,4 @@
-<div>Hello world!</div>
+<div><b>Hello world!</b></div>
 <script>
 	var div = document.createElement("div");
 	div.textContent = "it worked!";
diff --git a/test/demos/demo-without-js.html b/test/demos/demo-without-js.html
new file mode 100644
index 0000000..c3e8f90
--- /dev/null
+++ b/test/demos/demo-without-js.html
@@ -0,0 +1 @@
+<div id="demo-html"><b>Hello world!</b></div>
\ No newline at end of file