diff --git a/test/protos b/test/protos
new file mode 120000
index 0000000000..33fd406609
--- /dev/null
+++ b/test/protos
@@ -0,0 +1 @@
+../protos/
\ No newline at end of file
diff --git a/test/test_views.html b/test/test_views.html
new file mode 100644
index 0000000000..a156511d8c
--- /dev/null
+++ b/test/test_views.html
@@ -0,0 +1,152 @@
+
+
+
+
+
+ TextSecure test runner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/views/message_view_test.js b/test/views/message_view_test.js
new file mode 100644
index 0000000000..8f0e0eee8d
--- /dev/null
+++ b/test/views/message_view_test.js
@@ -0,0 +1,29 @@
+mocha.setup("bdd");
+window.assert = chai.assert;
+describe('MessageView', function() {
+ var message = Whisper.Messages.add({
+ threadId: 'test-thread',
+ body: 'hello world',
+ type: 'outgoing',
+ timestamp: new Date().getTime()
+ });
+
+ describe('#render', function() {
+ var view = new Whisper.MessageView({model: message});
+ var div = $('').append(view.render().$el);
+
+ it('should include the message text', function() {
+ assert.match(view.$el.html(), /hello world/);
+ });
+
+ it('should auto-update the message text', function() {
+ message.set('body', 'goodbye world');
+ assert.match(view.$el.html(), /goodbye world/);
+ });
+
+ it('should go away when the model is destroyed', function() {
+ message.destroy();
+ assert.strictEqual(div.find(view.$el).length, 0);
+ });
+ });
+});