diff --git a/lms/static/js/spec/courseware/bridge_spec.js b/lms/static/js/spec/courseware/bridge_spec.js
new file mode 100644
index 000000000000..c30d64e999a9
--- /dev/null
+++ b/lms/static/js/spec/courseware/bridge_spec.js
@@ -0,0 +1,98 @@
+describe('JS bridge for communication between native mobile apps and the xblock', function() {
+
+ beforeEach(function() {
+ // Mock objects for IOS and Android bridges
+ window.webkit = {
+ messageHandlers: {
+ IOSBridge: {
+ postMessage: jasmine.createSpy('postMessage')
+ }
+ }
+ };
+ window.AndroidBridge = {
+ postMessage: jasmine.createSpy('postMessage')
+ };
+ });
+
+ describe('sendMessageToIOS', function() {
+ it('should call postMessage on IOSBridge with the correct message', function() {
+ const message = JSON.stringify({answer: 'test'});
+ sendMessageToIOS(message);
+ expect(window.webkit.messageHandlers.IOSBridge.postMessage).toHaveBeenCalledWith(message);
+ });
+ });
+
+ describe('sendMessageToAndroid', function() {
+ it('should call postMessage on AndroidBridge with the correct message', function() {
+ const message = JSON.stringify({answer: 'test'});
+ sendMessageToAndroid(message);
+ expect(window.AndroidBridge.postMessage).toHaveBeenCalledWith(message);
+ });
+ });
+
+ describe('markProblemCompleted', function() {
+ it('should correctly parse the message and update the DOM elements', function() {
+ const message = JSON.stringify({
+ data: 'input1=answer1&input2=answer2'
+ });
+ const problemContainer = $('
');
+ $('body').append(problemContainer);
+
+ markProblemCompleted(message);
+
+ expect(problemContainer.find('.submit-attempt-container .submit').attr('disabled')).toBe('disabled');
+ expect(problemContainer.find('.notification-gentle-alert .notification-message').html()).toBe('Answer submitted.');
+ expect(problemContainer.find('.notification-gentle-alert').css('display')).not.toBe('none');
+ expect(problemContainer.find('#input1').val()).toBe('answer1');
+ expect(problemContainer.find('#input2').val()).toBe('answer2');
+ expect(problemContainer.find('#answer1').prop('disabled')).toBe(true);
+ expect(problemContainer.find('#answer2').prop('disabled')).toBe(true);
+
+ problemContainer.remove();
+ });
+ });
+
+ describe('$.ajax', function() {
+ beforeEach(function() {
+ spyOn($, 'ajax').and.callThrough();
+ });
+
+ it('should intercept the request to problem_check and send data to the native apps', function() {
+ const ajaxOptions = {
+ url: 'http://example.com/problem_check',
+ data: {answer: 'test'}
+ };
+
+ $.ajax(ajaxOptions);
+
+ expect(window.webkit.messageHandlers.IOSBridge.postMessage).toHaveBeenCalledWith(JSON.stringify(ajaxOptions));
+ expect(window.AndroidBridge.postMessage).toHaveBeenCalledWith(JSON.stringify(ajaxOptions));
+ });
+
+ it('should call the original $.ajax function', function() {
+ const ajaxOptions = {
+ url: 'http://example.com/problem_check',
+ data: {answer: 'test'}
+ };
+
+ const originalAjax = spyOn($, 'ajax').and.callFake(function(options) {
+ return originalAjax.and.callThrough().call(this, options);
+ });
+
+ $.ajax(ajaxOptions);
+
+ expect(originalAjax).toHaveBeenCalledWith(ajaxOptions);
+ });
+ });
+});