diff --git a/common/lib/xmodule/xmodule/css/lti/lti.scss b/common/lib/xmodule/xmodule/css/lti/lti.scss index 152963516c..00a9c6b969 100644 --- a/common/lib/xmodule/xmodule/css/lti/lti.scss +++ b/common/lib/xmodule/xmodule/css/lti/lti.scss @@ -30,6 +30,5 @@ div.lti { height: 800px; display: block; border: 0px; - overflow-x: hidden; } } diff --git a/common/lib/xmodule/xmodule/js/spec/lti/lti_spec.js b/common/lib/xmodule/xmodule/js/spec/lti/lti_spec.js deleted file mode 100644 index 9a9369eac1..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/lti/lti_spec.js +++ /dev/null @@ -1,370 +0,0 @@ -/** - * File: constructor.js - * - * Purpose: Jasmine tests for LTI module (front-end part). - * - * - * Because LTI module is constructed so that all methods are available via the - * prototype chain, many times we can test methods without having to - * instantiate a new LTI object. - */ - -/* - * "Hence that general is skillful in attack whose opponent does not know what - * to defend; and he is skillful in defense whose opponent does not know what - * to attack." - * - * ~ Sun Tzu - */ - -(function () { - var IN_NEW_WINDOW = 'true', - IN_IFRAME = 'false', - EMPTY_URL = '', - DEFAULT_URL = 'http://www.example.com', - NEW_URL = 'http://www.example.com/some_book'; - - describe('LTI XModule', function () { - describe('LTIConstructor method', function () { - describe('[in iframe, new url]', function () { - var lti; - - beforeEach(function () { - loadFixtures('lti.html'); - setUpLtiElement($('.lti-wrapper'), IN_IFRAME, NEW_URL); - - spyOnEvent( - $('.lti-wrapper').find('.ltiLaunchForm'), 'submit' - ); - - lti = new window.LTI('.lti-wrapper'); - }); - - it('new LTI object contains all properties', function () { - expect(lti.el).toBeDefined(); - expect(lti.el).toExist(); - - expect(lti.formEl).toBeDefined(); - expect(lti.formEl).toExist(); - expect(lti.formEl).toHaveAttr('action'); - - expect(lti.ltiEl).toBeDefined(); - expect(lti.ltiEl).toExist(); - - expect(lti.formAction).toEqual(NEW_URL); - expect(lti.openInANewPage).toEqual(false); - expect(lti.ajaxUrl).toEqual(jasmine.any(String)); - - expect('submit').toHaveBeenTriggeredOn(lti.formEl); - }); - - afterEach(function () { - lti = undefined; - }); - }); - - describe('[in new window, new url]', function () { - var lti; - - beforeEach(function () { - loadFixtures('lti.html'); - setUpLtiElement($('.lti-wrapper'), IN_NEW_WINDOW, NEW_URL); - - lti = new window.LTI('.lti-wrapper'); - }); - - it('check extra properties and values', function () { - expect(lti.openInANewPage).toEqual(true); - expect(lti.signatureIsNew).toBeTruthy(); - - expect(lti.newWindowBtnEl).toBeDefined(); - expect(lti.newWindowBtnEl).toExist(); - - expect(lti.disableOpenNewWindowBtn).toBe(false); - }); - - afterEach(function () { - lti = undefined; - }); - }); - - describe('[in iframe, NO new url]', function () { - var testCases = [{ - itDescription: 'URL is blank', - action: EMPTY_URL - }, { - itDescription: 'URL is default', - action: DEFAULT_URL - }]; - - $.each(testCases, function (index, test) { - it(test.itDescription, function () { - var lti; - - loadFixtures('lti.html'); - setUpLtiElement( - $('.lti-wrapper'), IN_IFRAME, test.action - ); - - lti = new window.LTI('.lti-wrapper'); - - expect(lti.openInANewPage).not.toBeDefined(); - }); - }); - }); - }); - - describe('submitFormHandler method', function () { - var thisObj; - - beforeEach(function () { - thisObj = { - signatureIsNew: undefined, - getNewSignature: jasmine.createSpy('getNewSignature'), - formEl: { - submit: jasmine.createSpy('submit') - } - }; - }); - - it('signature is new', function () { - thisObj.signatureIsNew = true; - - window.LTI.prototype.submitFormHandler.call(thisObj); - - expect(thisObj.formEl.submit).toHaveBeenCalled(); - expect(thisObj.signatureIsNew).toBe(false); - }); - - it('signature is old', function () { - thisObj.signatureIsNew = false; - - window.LTI.prototype.submitFormHandler.call(thisObj); - - expect(thisObj.formEl.submit).not.toHaveBeenCalled(); - expect(thisObj.signatureIsNew).toBe(false); - expect(thisObj.getNewSignature).toHaveBeenCalled(); - }); - - afterEach(function () { - thisObj = undefined; - }); - }); - - describe('getNewSignature method', function () { - var lti; - - beforeEach(function () { - loadFixtures('lti.html'); - setUpLtiElement($('.lti-wrapper'), IN_NEW_WINDOW, NEW_URL); - - spyOn($, 'postWithPrefix').andCallFake( - function (url, data, callback) { - callback({ - input_fields: {} - }); - } - ); - - lti = new window.LTI('.lti-wrapper'); - - spyOn(lti, 'submitFormHandler').andCallThrough(); - lti.submitFormHandler.reset(); - - spyOn(lti, 'handleAjaxUpdateSignature'); - }); - - it( - '"Open in new page" clicked twice, signature requested once', - function () { - lti.newWindowBtnEl.click(); - lti.newWindowBtnEl.click(); - - expect(lti.submitFormHandler).toHaveBeenCalled(); - expect(lti.submitFormHandler.callCount).toBe(2); - - expect($.postWithPrefix).toHaveBeenCalledWith( - lti.ajaxUrl + '/regenerate_signature', - {}, - jasmine.any(Function) - ); - - expect(lti.disableOpenNewWindowBtn).toBe(true); - - expect(lti.handleAjaxUpdateSignature) - .toHaveBeenCalledWith({ - input_fields: {} - }); - } - ); - - afterEach(function () { - lti = undefined; - }); - }); - - describe('handleAjaxUpdateSignature method', function () { - var lti, oldInputFields, newInputFields, - AjaxCallbackData = {}; - - function fakePostWithPrefix(url, data, callback) { - return callback(AjaxCallbackData); - } - - beforeEach(function () { - oldInputFields = { - oauth_nonce: '28347958723982798572', - oauth_timestamp: '2389479832', - oauth_signature: '89ru3289r3ry283y3r82ryr38yr' - }; - - newInputFields = { - oauth_nonce: 'ru3902ru239ru', - oauth_timestamp: '24ru309rur39r8u', - oauth_signature: '08923ru3082u2rur' - }; - - AjaxCallbackData.error = 0; - AjaxCallbackData.input_fields = newInputFields; - - loadFixtures('lti.html'); - setUpLtiElement($('.lti-wrapper'), IN_NEW_WINDOW, NEW_URL); - - spyOn($, 'postWithPrefix').andCallFake(fakePostWithPrefix); - - lti = new window.LTI('.lti-wrapper'); - - spyOn(lti, 'submitFormHandler').andCallThrough(); - spyOn(lti, 'handleAjaxUpdateSignature').andCallThrough(); - spyOn(lti.formEl, 'submit'); - spyOn(window.console, 'log').andCallThrough(); - - lti.submitFormHandler.reset(); - lti.handleAjaxUpdateSignature.reset(); - lti.formEl.submit.reset(); - window.console.log.reset(); - }); - - it('On second click form is updated, and submitted', function () { - // Setup initial OAuth values in the form. - lti.formEl.find("input[name='oauth_nonce']") - .val(oldInputFields.oauth_nonce); - lti.formEl.find("input[name='oauth_timestamp']") - .val(oldInputFields.oauth_timestamp); - lti.formEl.find("input[name='oauth_signature']") - .val(oldInputFields.oauth_signature); - - // First click. Signature is new. Should just submit the form. - lti.newWindowBtnEl.click(); - - // Initial OAuth values should not have changed. - expect(lti.formEl.find("input[name='oauth_nonce']").val()) - .toBe(oldInputFields.oauth_nonce); - expect(lti.formEl.find("input[name='oauth_timestamp']").val()) - .toBe(oldInputFields.oauth_timestamp); - expect(lti.formEl.find("input[name='oauth_signature']").val()) - .toBe(oldInputFields.oauth_signature); - - expect(lti.submitFormHandler).toHaveBeenCalled(); - expect(lti.submitFormHandler.callCount).toBe(1); - - expect(lti.handleAjaxUpdateSignature).not.toHaveBeenCalled(); - expect(lti.handleAjaxUpdateSignature.callCount).toBe(0); - - expect(lti.formEl.submit).toHaveBeenCalled(); - expect(lti.formEl.submit.callCount).toBe(1); - - lti.submitFormHandler.reset(); - lti.handleAjaxUpdateSignature.reset(); - lti.formEl.submit.reset(); - - // Second click. Signature is old. Should request for a new - // signature, and then submit the form. - lti.newWindowBtnEl.click(); - - expect(lti.submitFormHandler).toHaveBeenCalled(); - expect(lti.submitFormHandler.callCount).toBe(2); - - expect(lti.handleAjaxUpdateSignature).toHaveBeenCalled(); - expect(lti.handleAjaxUpdateSignature.callCount).toBe(1); - - expect(lti.formEl.submit).toHaveBeenCalled(); - expect(lti.formEl.submit.callCount).toBe(1); - - expect(lti.disableOpenNewWindowBtn).toBe(false); - - // The new OAuth values should be in the form. - expect(lti.formEl.find("input[name='oauth_nonce']").val()) - .toBe(newInputFields.oauth_nonce); - expect(lti.formEl.find("input[name='oauth_timestamp']").val()) - .toBe(newInputFields.oauth_timestamp); - expect(lti.formEl.find("input[name='oauth_signature']").val()) - .toBe(newInputFields.oauth_signature); - }); - - it('invalid response for new OAuth signature', function () { - AjaxCallbackData.input_fields = 0; - AjaxCallbackData.error = 'error'; - - lti.newWindowBtnEl.click(); - - lti.submitFormHandler.reset(); - lti.handleAjaxUpdateSignature.reset(); - window.console.log.reset(); - lti.formEl.submit.reset(); - - lti.newWindowBtnEl.click(); - - expect(lti.submitFormHandler).toHaveBeenCalled(); - expect(lti.submitFormHandler.callCount).toBe(1); - - expect(lti.handleAjaxUpdateSignature).toHaveBeenCalled(); - expect(lti.handleAjaxUpdateSignature.callCount).toBe(1); - - expect(window.console.log).toHaveBeenCalledWith( - jasmine.any(String) - ); - - expect(lti.formEl.submit).not.toHaveBeenCalled(); - }); - - afterEach(function () { - lti = undefined; - oldInputFields = undefined; - newInputFields = undefined; - }); - }); - }); - - function setUpLtiElement(element, target, action) { - var container, form; - - container = element.find('.lti'); - form = container.find('.ltiLaunchForm'); - - if (target === IN_IFRAME) { - container.data('open_in_a_new_page', 'false'); - form.attr('target', 'ltiLaunchFrame'); - } - - form.attr('action', action); - - // If we have a new proper action (non-default), we create either - // a link that will submit the form, or an iframe that will contain - // the answer of auto submitted form. - if (action !== EMPTY_URL && action !== DEFAULT_URL) { - if (target === IN_NEW_WINDOW) { - $('', { - href: '#', - class: 'link_lti_new_window' - }).appendTo(container); - } else { - $('', { - name: 'ltiLaunchFrame', - class: 'ltiLaunchFrame', - src: '' - }).appendTo(container); - } - } - } -}()); diff --git a/common/lib/xmodule/xmodule/js/src/lti/01_lti.js b/common/lib/xmodule/xmodule/js/src/lti/01_lti.js deleted file mode 100644 index 4803c05e78..0000000000 --- a/common/lib/xmodule/xmodule/js/src/lti/01_lti.js +++ /dev/null @@ -1,192 +0,0 @@ -/** - * File: lti.js - * - * Purpose: LTI module constructor. Given an LTI element, we process it. - * - * - * Inside the element there is a form. If that form has a valid action - * attribute, then we do one of: - * - * 1.) Submit the form. The results will be shown on the current page in an - * iframe. - * 2.) Attach a handler function to a link which will submit the form. The - * results will be shown in a new window. - * - * The 'open_in_a_new_page' data attribute of the LTI element dictates which of - * the two actions will be performed. - */ - -/* - * So the thing to do when working on a motorcycle, as in any other task, is to - * cultivate the peace of mind which does not separate one's self from one's - * surroundings. When that is done successfully, then everything else follows - * naturally. Peace of mind produces right values, right values produce right - * thoughts. Right thoughts produce right actions and right actions produce - * work which will be a material reflection for others to see of the serenity - * at the center of it all. - * - * ~ Robert M. Pirsig - */ - -(function (requirejs, require, define) { - -// JavaScript LTI XModule -define( -'lti/01_lti.js', -[], -function () { - - var LTI = LTIConstructor; - - LTI.prototype = { - submitFormHandler: submitFormHandler, - getNewSignature: getNewSignature, - handleAjaxUpdateSignature: handleAjaxUpdateSignature - }; - - return LTI; - - // JavaScript LTI XModule constructor - function LTIConstructor(element) { - var _this = this; - - // In cms (Studio) the element is already a jQuery object. In lms it is - // a DOM object. - // - // To make sure that there is no error, we pass it through the $() - // function. This will make it a jQuery object if it isn't already so. - this.el = $(element); - - this.formEl = this.el.find('.ltiLaunchForm'); - this.formAction = this.formEl.attr('action'); - - // If action is empty string, or action is the default URL that should - // not cause a form submit. - if (!this.formAction || this.formAction === 'http://www.example.com') { - - // Nothing to do - no valid action provided. Error message will be - // displaced in browser (HTML). - return; - } - - this.ltiEl = this.el.find('.lti'); - - // We want a Boolean 'true' or 'false'. First we will retrieve the data - // attribute. - this.openInANewPage = this.ltiEl.data('open_in_a_new_page'); - // Then we will parse it via native JSON.parse(). - this.openInANewPage = JSON.parse(this.openInANewPage); - - // The URL where we can request for a new OAuth signature for form - // submission to the LTI provider. - this.ajaxUrl = this.ltiEl.data('ajax_url'); - - // The OAuth signature can only be used once (because of timestamp - // and nonce). This will be reset each time the form is submitted so - // that we know to fetch a new OAuth signature on subsequent form - // submit. - this.signatureIsNew = true; - - // If the Form's action attribute is set (i.e. we can perform a normal - // submit), then we (depending on instance settings) submit the form - // when user will click on a link, or submit the form immediately. - if (this.openInANewPage === true) { - // From the start, the button is enabled. - this.disableOpenNewWindowBtn = false; - - this.newWindowBtnEl = this.el.find('.link_lti_new_window') - .on( - 'click', - function () { - // Don't allow clicking repeatedly on this button - // if we are waiting for an AJAX response (with new - // OAuth signature). - if (_this.disableOpenNewWindowBtn === true) { - return; - } - - return _this.submitFormHandler(); - } - ); - } else { - // At this stage the form exists on the page and has a valid - // action. We are safe to submit it, even if `openInANewPage` is - // set to some weird value. - this.submitFormHandler(); - } - } - - // The form submit handler. Before the form is submitted, we must check if - // the OAuth signature is new (valid). If it is not new, block form - // submission and request for a signature. After a new signature is - // fetched, the form will be submitted. - function submitFormHandler() { - if (this.signatureIsNew) { - // Continue with submitting the form. - this.formEl.submit(); - - // If the OAuth signature is new, mark it as old. - this.signatureIsNew = false; - - // If we have an "Open LTI in a new window" button. - if (this.newWindowBtnEl) { - // Enable clicking on the button again. - this.disableOpenNewWindowBtn = false; - } - } else { - // The OAuth signature is old. Request for a new OAuth signature. - // - // Don't submit the form. It will be submitted once a new OAuth - // signature is received. - this.getNewSignature(); - } - } - - // Request form the server a new OAuth signature. - function getNewSignature() { - var _this = this; - - // If we have an "Open LTI in a new window" button. - if (this.newWindowBtnEl) { - // Make sure that while we are waiting for a new signature, the - // user can't click on the "Open LTI in a new window" button - // repeatedly. - this.disableOpenNewWindowBtn = true; - } - - $.postWithPrefix( - this.ajaxUrl + '/regenerate_signature', - {}, - function (response) { - return _this.handleAjaxUpdateSignature(response); - } - ); - } - - // When a new OAuth signature is received, and if the data received back is - // OK, update the form, and submit it. - function handleAjaxUpdateSignature(response) { - var _this = this; - - // If the response is valid, and contains expected data. - if ($.isPlainObject(response.input_fields)) { - // We received a new OAuth signature. - this.signatureIsNew = true; - - // Update the form fields with new data, and new OAuth - // signature. - $.each(response.input_fields, function (name, value) { - var inputEl = _this.formEl.find("input[name='" + name + "']"); - - inputEl.val(value); - }); - - // Submit the form. - this.submitFormHandler(); - } else { - console.log('[LTI info]: ' + response.error); - } - } -}); - -}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); diff --git a/common/lib/xmodule/xmodule/js/src/lti/02_main.js b/common/lib/xmodule/xmodule/js/src/lti/02_main.js deleted file mode 100644 index 9a6dacd83b..0000000000 --- a/common/lib/xmodule/xmodule/js/src/lti/02_main.js +++ /dev/null @@ -1,57 +0,0 @@ -(function (requirejs, require, define) { - -// In the case when the LTI constructor will be called before -// RequireJS finishes loading all of the LTI dependencies, we will have -// a mock function that will collect all the elements that must be -// initialized as LTI elements. -// -// Once RequireJS will load all of the necessary dependencies, main code -// will invoke the mock function with the second parameter set to truthy value. -// This will trigger the actual LTI constructor on all elements that -// are stored in a temporary list. -window.LTI = (function () { - // Temporary storage place for elements that must be initialized as LTI - // elements. - var tempCallStack = []; - - return function (element, processTempCallStack) { - // If mock function was called with second parameter set to truthy - // value, we invoke the real `window.LTI` on all the stored elements - // so far. - if (processTempCallStack) { - $.each(tempCallStack, function (index, element) { - // By now, `window.LTI` is the real constructor. - window.LTI(element); - }); - - return; - } - - // If normal call to `window.LTI` constructor, store the element - // for later initializing. - tempCallStack.push(element); - - // Real LTI constructor returns `undefined`. The mock constructor will - // return the same value. Making this explicit. - return undefined; - }; -}()); - -// Main module. -require( -[ - 'lti/01_lti.js' -], -function ( - LTIConstructor -) { - var oldLTI = window.LTI; - - window.LTI = LTIConstructor; - - // Invoke the mock LTI constructor so that the elements stored within - // it can be processed by the real `window.LTI` constructor. - oldLTI(null, true); -}); - -}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py index 087bf67770..edb990093a 100644 --- a/common/lib/xmodule/xmodule/lti_module.py +++ b/common/lib/xmodule/xmodule/lti_module.py @@ -180,14 +180,7 @@ class LTIModule(LTIFields, XModule): Otherwise error message from LTI provider is generated. """ - js = { - 'js': [ - resource_string(__name__, 'js/src/lti/01_lti.js'), - resource_string(__name__, 'js/src/lti/02_main.js') - ] - } css = {'scss': [resource_string(__name__, 'css/lti/lti.scss')]} - js_module_name = "LTI" def get_input_fields(self): # LTI provides a list of default parameters that might be passed as @@ -253,12 +246,11 @@ class LTIModule(LTIFields, XModule): client_secret, ) - def get_html(self): + def get_context(self): """ - Renders parameters to template. + Returns a context. """ - - context = { + return { 'input_fields': self.get_input_fields(), # These parameters do not participate in OAuth signing. @@ -267,12 +259,27 @@ class LTIModule(LTIFields, XModule): 'element_class': self.category, 'open_in_a_new_page': self.open_in_a_new_page, 'display_name': self.display_name, - 'ajax_url': self.system.ajax_url, + 'form_url': self.get_form_path(), } - return self.system.render_template('lti.html', context) - def handle_ajax(self, dispatch, __): + def get_form_path(self): + return self.runtime.handler_url(self, 'preview_handler').rstrip('/?') + + def get_html(self): + """ + Renders parameters to template. + """ + return self.system.render_template('lti.html', self.get_context()) + + def get_form(self): + """ + Renders parameters to form template. + """ + return self.system.render_template('lti_form.html', self.get_context()) + + @XBlock.handler + def preview_handler(self, request, dispatch): """ Ajax handler. @@ -282,10 +289,7 @@ class LTIModule(LTIFields, XModule): Returns: json string """ - if dispatch == 'regenerate_signature': - return json.dumps({ 'input_fields': self.get_input_fields() }) - else: # return error message - return json.dumps({ 'error': '[handle_ajax]: Unknown Command!' }) + return Response(self.get_form(), content_type='text/html') def get_user_id(self): user_id = self.runtime.anonymous_student_id @@ -614,3 +618,4 @@ class LTIDescriptor(LTIFields, MetadataOnlyEditingDescriptor, EmptyDataRawDescri """ module_class = LTIModule grade_handler = module_attr('grade_handler') + preview_handler = module_attr('preview_handler') diff --git a/common/lib/xmodule/xmodule/tests/test_lti_unit.py b/common/lib/xmodule/xmodule/tests/test_lti_unit.py index 3dd6c011fd..4caa9c732a 100644 --- a/common/lib/xmodule/xmodule/tests/test_lti_unit.py +++ b/common/lib/xmodule/xmodule/tests/test_lti_unit.py @@ -229,6 +229,12 @@ class LTIModuleTest(LogicTest): real_outcome_service_url = self.xmodule.get_outcome_service_url() self.assertEqual(real_outcome_service_url, expected_outcome_service_url) + def test_get_form_path(self): + expected_form_path = self.xmodule.runtime.handler_url(self.xmodule, 'preview_handler').rstrip('/?') + + real_form_path = self.xmodule.get_form_path() + self.assertEqual(real_form_path, expected_form_path) + def test_resource_link_id(self): with patch('xmodule.lti_module.LTIModule.id', new_callable=PropertyMock) as mock_id: mock_id.return_value = self.module_id @@ -251,28 +257,6 @@ class LTIModuleTest(LogicTest): def test_client_key_secret(self): pass - def test_handle_ajax(self): - dispatch = 'regenerate_signature' - data = '' - self.xmodule.get_input_fields = Mock(return_value={'test_input_field_key': 'test_input_field_value'}) - json_dump = self.xmodule.handle_ajax(dispatch, data) - expected_json_dump = '{"input_fields": {"test_input_field_key": "test_input_field_value"}}' - self.assertEqual( - json.loads(json_dump), - json.loads(expected_json_dump) - ) - - def test_handle_ajax_bad_dispatch(self): - dispatch = 'bad_dispatch' - data = '' - self.xmodule.get_input_fields = Mock(return_value={'test_input_field_key': 'test_input_field_value'}) - json_dump = self.xmodule.handle_ajax(dispatch, data) - expected_json_dump = '{"error": "[handle_ajax]: Unknown Command!"}' - self.assertEqual( - json.loads(json_dump), - json.loads(expected_json_dump) - ) - def test_max_score(self): self.xmodule.weight = 100.0 diff --git a/lms/djangoapps/courseware/features/lti.py b/lms/djangoapps/courseware/features/lti.py index 47faa45e83..f2b80a8787 100644 --- a/lms/djangoapps/courseware/features/lti.py +++ b/lms/djangoapps/courseware/features/lti.py @@ -13,22 +13,22 @@ from courseware.tests.factories import InstructorFactory @step('I view the LTI and error is shown$') def lti_is_not_rendered(_step): # error is shown - assert world.is_css_present('.error_message') + assert world.is_css_present('.error_message', wait_time=0) # iframe is not presented - assert not world.is_css_present('iframe') + assert not world.is_css_present('iframe', wait_time=0) # link is not presented - assert not world.is_css_present('.link_lti_new_window') + assert not world.is_css_present('.link_lti_new_window', wait_time=0) def check_lti_iframe_content(text): #inside iframe test content is presented location = world.scenario_dict['LTI'].location.html_id() - iframe_name = 'ltiLaunchFrame-' + location + iframe_name = 'ltiFrame-' + location with world.browser.get_iframe(iframe_name) as iframe: # iframe does not contain functions from terrain/ui_helpers.py - assert iframe.is_element_present_by_css('.result', wait_time=5) + assert iframe.is_element_present_by_css('.result', wait_time=0) assert (text == world.retry_on_exception( lambda: iframe.find_by_css('.result')[0].text, max_attempts=5 @@ -38,18 +38,18 @@ def check_lti_iframe_content(text): @step('I view the LTI and it is rendered in (.*)$') def lti_is_rendered(_step, rendered_in): if rendered_in.strip() == 'iframe': - assert world.is_css_present('iframe') - assert not world.is_css_present('.link_lti_new_window') - assert not world.is_css_present('.error_message') + assert world.is_css_present('iframe', wait_time=2) + assert not world.is_css_present('.link_lti_new_window', wait_time=0) + assert not world.is_css_present('.error_message', wait_time=0) # iframe is visible assert world.css_visible('iframe') check_lti_iframe_content("This is LTI tool. Success.") elif rendered_in.strip() == 'new page': - assert not world.is_css_present('iframe') - assert world.is_css_present('.link_lti_new_window') - assert not world.is_css_present('.error_message') + assert not world.is_css_present('iframe', wait_time=2) + assert world.is_css_present('.link_lti_new_window', wait_time=0) + assert not world.is_css_present('.error_message', wait_time=0) check_lti_popup() else: # incorrent rendered_in parameter assert False @@ -57,9 +57,9 @@ def lti_is_rendered(_step, rendered_in): @step('I view the LTI but incorrect_signature warning is rendered$') def incorrect_lti_is_rendered(_step): - assert world.is_css_present('iframe') - assert not world.is_css_present('.link_lti_new_window') - assert not world.is_css_present('.error_message') + assert world.is_css_present('iframe', wait_time=2) + assert not world.is_css_present('.link_lti_new_window', wait_time=0) + assert not world.is_css_present('.error_message', wait_time=0) #inside iframe test content is presented check_lti_iframe_content("Wrong LTI signature") @@ -234,10 +234,11 @@ def check_progress(_step, text): @step('I see graph with total progress "([^"]*)"$') def see_graph(_step, progress): SELECTOR = 'grade-detail-graph' - node = world.browser.find_by_xpath('//div[@id="{parent}"]//div[text()="{progress}"]'.format( + XPATH = '//div[@id="{parent}"]//div[text()="{progress}"]'.format( parent=SELECTOR, progress=progress, - )) + ) + node = world.browser.find_by_xpath(XPATH) assert node @@ -259,7 +260,7 @@ def see_value_in_the_gradebook(_step, label, text): @step('I submit answer to LTI question$') def click_grade(_step): location = world.scenario_dict['LTI'].location.html_id() - iframe_name = 'ltiLaunchFrame-' + location + iframe_name = 'ltiFrame-' + location with world.browser.get_iframe(iframe_name) as iframe: iframe.find_by_name('submit-button').first.click() assert iframe.is_text_present('LTI consumer (edX) responded with XML content') diff --git a/lms/djangoapps/courseware/tests/test_lti_integration.py b/lms/djangoapps/courseware/tests/test_lti_integration.py index ab2a575a0e..7faa40d2f9 100644 --- a/lms/djangoapps/courseware/tests/test_lti_integration.py +++ b/lms/djangoapps/courseware/tests/test_lti_integration.py @@ -5,8 +5,6 @@ from . import BaseTestXmodule from collections import OrderedDict import mock import urllib -from xmodule.lti_module import LTIModule -from mock import Mock class TestLTI(BaseTestXmodule): @@ -85,7 +83,6 @@ class TestLTI(BaseTestXmodule): Makes sure that all parameters extracted. """ generated_context = self.item_module.render('student_view').content - expected_context = { 'display_name': self.item_module.display_name, 'input_fields': self.correct_headers, @@ -93,7 +90,7 @@ class TestLTI(BaseTestXmodule): 'element_id': self.item_module.location.html_id(), 'launch_url': 'http://www.example.com', # default value 'open_in_a_new_page': True, - 'ajax_url': self.item_descriptor.xmodule_runtime.ajax_url, + 'form_url': self.item_descriptor.xmodule_runtime.handler_url(self.item_module, 'preview_handler').rstrip('/?'), } self.assertEqual( diff --git a/lms/templates/lti.html b/lms/templates/lti.html index 4a6ee870c7..0018554528 100644 --- a/lms/templates/lti.html +++ b/lms/templates/lti.html @@ -4,40 +4,15 @@
+
${_('View resource in a new window')}