container_subviews_spec.js 33.4 KB
Newer Older
1 2
define(["jquery", "underscore", "underscore.string", "common/js/spec_helpers/ajax_helpers",
        "common/js/spec_helpers/template_helpers", "js/spec_helpers/edit_helpers",
3 4
        "common/js/components/views/feedback_prompt", "js/views/pages/container",
        "js/views/pages/container_subviews", "js/models/xblock_info", "js/views/utils/xblock_utils"],
5
    function ($, _, str, AjaxHelpers, TemplateHelpers, EditHelpers, Prompt, ContainerPage, ContainerSubviews,
6 7
              XBlockInfo, XBlockUtils) {
        var VisibilityState = XBlockUtils.VisibilityState;
8 9

        describe("Container Subviews", function() {
10
            var model, containerPage, requests, createContainerPage, renderContainerPage,
11
                respondWithHtml, fetch,
12
                disabledCss = "is-disabled", defaultXBlockInfo, createXBlockInfo,
13 14 15 16
                mockContainerPage = readFixtures('mock/mock-container-page.underscore'),
                mockContainerXBlockHtml = readFixtures('mock/mock-empty-container-xblock.underscore');

            beforeEach(function () {
17 18 19 20 21
                TemplateHelpers.installTemplate('xblock-string-field-editor');
                TemplateHelpers.installTemplate('publish-xblock');
                TemplateHelpers.installTemplate('publish-history');
                TemplateHelpers.installTemplate('unit-outline');
                TemplateHelpers.installTemplate('container-message');
22
                appendSetFixtures(mockContainerPage);
23
            });
24

25 26 27 28
            defaultXBlockInfo = {
                id: 'locator-container',
                display_name: 'Test Container',
                category: 'vertical',
29 30
                published: false,
                has_changes: false,
31
                visibility_state: VisibilityState.unscheduled,
32 33 34 35 36 37 38 39 40 41
                edited_on: "Jul 02, 2014 at 14:20 UTC", edited_by: "joe",
                published_on: "Jul 01, 2014 at 12:45 UTC", published_by: "amako",
                currently_visible_to_students: false
            };

            createXBlockInfo = function(options) {
                return _.extend(_.extend({}, defaultXBlockInfo), options || {});
            };

            createContainerPage = function (test, options) {
42
                requests = AjaxHelpers.requests(test);
43
                model = new XBlockInfo(createXBlockInfo(options), { parse: true });
44 45
                containerPage = new ContainerPage({
                    model: model,
46
                    templates: EditHelpers.mockComponentTemplates,
47 48 49
                    el: $('#content'),
                    isUnitPage: true
                });
50
            };
51

52 53
            renderContainerPage = function (test, html, options) {
                createContainerPage(test, options);
54
                containerPage.render();
55
                respondWithHtml(html, options);
56 57
            };

58
            respondWithHtml = function(html, options) {
59
                AjaxHelpers.respondWithJson(
60
                    requests,
61
                    { html: html, "resources": [] }
62
                );
63 64
                AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
                AjaxHelpers.respondWithJson(requests, createXBlockInfo(options));
65 66 67
            };

            fetch = function (json) {
68
                json = createXBlockInfo(json);
69
                model.fetch();
70 71
                AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
                AjaxHelpers.respondWithJson(requests, json);
72 73
            };

74
            describe("ViewLiveButtonController", function () {
75
                var viewPublishedCss = '.button-view',
76
                    visibilityNoteCss = '.note-visibility';
77

78
                it('renders correctly for unscheduled unit', function () {
79
                    renderContainerPage(this, mockContainerXBlockHtml);
80
                    expect(containerPage.$(viewPublishedCss)).toHaveClass(disabledCss);
81 82 83
                    expect(containerPage.$(viewPublishedCss).attr('title')).toBe("Open the courseware in the LMS");
                    expect(containerPage.$('.button-preview')).not.toHaveClass(disabledCss);
                    expect(containerPage.$('.button-preview').attr('title')).toBe("Preview the courseware in the LMS");
84 85
                });

86
                it('updates when publish state changes', function () {
87
                    renderContainerPage(this, mockContainerXBlockHtml);
88
                    fetch({published: true});
89 90
                    expect(containerPage.$(viewPublishedCss)).not.toHaveClass(disabledCss);

91
                    fetch({published: false});
92 93 94
                    expect(containerPage.$(viewPublishedCss)).toHaveClass(disabledCss);
                });

95 96 97 98 99 100 101 102 103 104 105
                it('updates when has_content_group_components attribute changes', function () {
                    renderContainerPage(this, mockContainerXBlockHtml);
                    fetch({has_content_group_components: false});
                    expect(containerPage.$(visibilityNoteCss).length).toBe(0);

                    fetch({has_content_group_components: true});
                    expect(containerPage.$(visibilityNoteCss).length).toBe(1);

                    fetch({has_content_group_components: false});
                    expect(containerPage.$(visibilityNoteCss).length).toBe(0);
                });
106 107 108 109 110
            });

            describe("Publisher", function () {
                var headerCss = '.pub-status',
                    bitPublishingCss = "div.bit-publishing",
111 112 113
                    liveClass = "is-live",
                    readyClass = "is-ready",
                    staffOnlyClass = "is-staff-only",
114 115
                    scheduledClass = "is-scheduled",
                    unscheduledClass = "",
116
                    hasWarningsClass = 'has-warnings',
117 118
                    publishButtonCss = ".action-publish",
                    discardChangesButtonCss = ".action-discard",
119
                    lastDraftCss = ".wrapper-last-draft",
120 121
                    releaseDateTitleCss = ".wrapper-release .title",
                    releaseDateContentCss = ".wrapper-release .copy",
122 123
                    releaseDateDateCss = ".wrapper-release .copy .release-date",
                    releaseDateWithCss = ".wrapper-release .copy .release-with",
124
                    promptSpies, sendDiscardChangesToServer, verifyPublishingBitUnscheduled;
125

126
                sendDiscardChangesToServer = function() {
127
                    // Helper function to do the discard operation, up until the server response.
128 129
                    containerPage.render();
                    respondWithHtml(mockContainerXBlockHtml);
130
                    fetch({published: true, has_changes: true, visibility_state: VisibilityState.needsAttention});
131
                    expect(containerPage.$(discardChangesButtonCss)).not.toHaveClass('is-disabled');
132
                    expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
133 134 135 136 137 138 139
                    // Click discard changes
                    containerPage.$(discardChangesButtonCss).click();

                    // Confirm the discard.
                    expect(promptSpies.constructor).toHaveBeenCalled();
                    promptSpies.constructor.mostRecentCall.args[0].actions.primary.click(promptSpies);

140
                    AjaxHelpers.expectJsonRequest(requests, "POST", "/xblock/locator-container",
cahrens committed
141 142
                        {"publish": "discard_changes"}
                    );
143 144
                };

145 146 147 148 149 150 151 152 153
                verifyPublishingBitUnscheduled = function() {
                    expect(containerPage.$(bitPublishingCss)).not.toHaveClass(liveClass);
                    expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
                    expect(containerPage.$(bitPublishingCss)).not.toHaveClass(hasWarningsClass);
                    expect(containerPage.$(bitPublishingCss)).not.toHaveClass(staffOnlyClass);
                    expect(containerPage.$(bitPublishingCss)).not.toHaveClass(scheduledClass);
                    expect(containerPage.$(bitPublishingCss)).toHaveClass(unscheduledClass);
                };

154 155 156 157 158
                beforeEach(function() {
                    promptSpies = spyOnConstructor(Prompt, "Warning", ["show", "hide"]);
                    promptSpies.show.andReturn(this.promptSpies);
                });

159
                it('renders correctly with private content', function () {
160 161
                    var verifyPrivateState = function() {
                        expect(containerPage.$(headerCss).text()).toContain('Draft (Never published)');
162 163
                        expect(containerPage.$(publishButtonCss)).not.toHaveClass(disabledCss);
                        expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
164
                        expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
165 166
                        expect(containerPage.$(bitPublishingCss)).not.toHaveClass(scheduledClass);
                        expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
167
                    };
168
                    renderContainerPage(this, mockContainerXBlockHtml);
169
                    fetch({published: false, has_changes: false, visibility_state: VisibilityState.needsAttention});
170 171
                    verifyPrivateState();

172
                    fetch({published: false, has_changes: true, visibility_state: VisibilityState.needsAttention});
173 174 175
                    verifyPrivateState();
                });

176
                it('renders correctly with published content', function () {
177
                    renderContainerPage(this, mockContainerXBlockHtml);
178 179 180 181
                    fetch({
                        published: true, has_changes: false, visibility_state: VisibilityState.ready,
                        release_date: "Jul 02, 2030 at 14:20 UTC"
                    });
182
                    expect(containerPage.$(headerCss).text()).toContain('Published (not yet released)');
183 184
                    expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
                    expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
185
                    expect(containerPage.$(bitPublishingCss)).toHaveClass(readyClass);
186
                    expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
187

188 189 190 191
                    fetch({
                        published: true, has_changes: true, visibility_state: VisibilityState.needsAttention,
                        release_date: "Jul 02, 2030 at 14:20 UTC"
                    });
192 193 194
                    expect(containerPage.$(headerCss).text()).toContain('Draft (Unpublished changes)');
                    expect(containerPage.$(publishButtonCss)).not.toHaveClass(disabledCss);
                    expect(containerPage.$(discardChangesButtonCss)).not.toHaveClass(disabledCss);
195
                    expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
196
                    expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
197

198 199 200
                    fetch({published: true, has_changes: false, visibility_state: VisibilityState.live,
                        release_date: "Jul 02, 1990 at 14:20 UTC"
                    });
201
                    expect(containerPage.$(headerCss).text()).toContain('Published and Live');
202 203 204
                    expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
                    expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
                    expect(containerPage.$(bitPublishingCss)).toHaveClass(liveClass);
205 206 207 208 209
                    expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);

                    fetch({published: true, has_changes: false, visibility_state: VisibilityState.unscheduled,
                        release_date: null
                    });
210
                    expect(containerPage.$(headerCss).text()).toContain('Published (not yet released)');
211 212 213
                    expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
                    expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
                    verifyPublishingBitUnscheduled();
214 215 216
                });

                it('can publish private content', function () {
217
                    var notificationSpy = EditHelpers.createNotificationSpy();
218
                    renderContainerPage(this, mockContainerXBlockHtml);
219
                    expect(containerPage.$(bitPublishingCss)).not.toHaveClass(hasWarningsClass);
220
                    expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
221
                    expect(containerPage.$(bitPublishingCss)).not.toHaveClass(liveClass);
222 223 224

                    // Click publish
                    containerPage.$(publishButtonCss).click();
225
                    EditHelpers.verifyNotificationShowing(notificationSpy, /Publishing/);
226

227
                    AjaxHelpers.expectJsonRequest(requests, "POST", "/xblock/locator-container",
cahrens committed
228 229
                        {"publish": "make_public"}
                    );
230 231

                    // Response to publish call
232
                    AjaxHelpers.respondWithJson(requests, {"id": "locator-container", "data": null, "metadata":{}});
233
                    EditHelpers.verifyNotificationHidden(notificationSpy);
234

235
                    AjaxHelpers.expectJsonRequest(requests, "GET", "/xblock/locator-container");
236
                    // Response to fetch
237 238 239 240 241 242
                    AjaxHelpers.respondWithJson(
                        requests,
                        createXBlockInfo({
                            published: true, has_changes: false, visibility_state: VisibilityState.ready
                        })
                    );
243 244

                    // Verify updates displayed
245
                    expect(containerPage.$(bitPublishingCss)).toHaveClass(readyClass);
246 247
                    // Verify that the "published" value has been cleared out of the model.
                    expect(containerPage.model.get("publish")).toBeNull();
248 249
                });

250
                it('does not refresh if publish fails', function () {
251
                    renderContainerPage(this, mockContainerXBlockHtml);
252
                    verifyPublishingBitUnscheduled();
253 254 255 256 257

                    // Click publish
                    containerPage.$(publishButtonCss).click();

                    // Respond with failure
258
                    AjaxHelpers.respondWithError(requests);
259
                    AjaxHelpers.expectNoRequests(requests);
260

261 262
                    // Verify still in draft (unscheduled) state.
                    verifyPublishingBitUnscheduled();
263 264
                    // Verify that the "published" value has been cleared out of the model.
                    expect(containerPage.model.get("publish")).toBeNull();
265 266 267
                });

                it('can discard changes', function () {
268 269
                    var notificationSpy, renderPageSpy, numRequests;
                    createContainerPage(this);
270
                    notificationSpy = EditHelpers.createNotificationSpy();
271
                    renderPageSpy = spyOn(containerPage.xblockPublisher, 'renderPage').andCallThrough();
cahrens committed
272

273
                    sendDiscardChangesToServer();
cahrens committed
274
                    numRequests = requests.length;
275

cahrens committed
276
                    // Respond with success.
277
                    AjaxHelpers.respondWithJson(requests, {"id": "locator-container"});
278
                    EditHelpers.verifyNotificationHidden(notificationSpy);
cahrens committed
279 280

                    // Verify other requests are sent to the server to update page state.
281
                    // Response to fetch, specifying the very next request (as multiple requests will be sent to server)
cahrens committed
282 283 284
                    expect(requests.length > numRequests).toBeTruthy();
                    expect(containerPage.model.get("publish")).toBeNull();
                    expect(renderPageSpy).toHaveBeenCalled();
285
                });
286

287
                it('does not fetch if discard changes fails', function () {
288 289 290
                    var renderPageSpy, numRequests;
                    createContainerPage(this);
                    renderPageSpy = spyOn(containerPage.xblockPublisher, 'renderPage').andCallThrough();
cahrens committed
291

292
                    sendDiscardChangesToServer();
293 294

                    // Respond with failure
295
                    AjaxHelpers.respondWithError(requests);
296
                    AjaxHelpers.expectNoRequests(requests);
297
                    expect(containerPage.$(discardChangesButtonCss)).not.toHaveClass('is-disabled');
cahrens committed
298 299
                    expect(containerPage.model.get("publish")).toBeNull();
                    expect(renderPageSpy).not.toHaveBeenCalled();
300 301 302
                });

                it('does not discard changes on cancel', function () {
303
                    renderContainerPage(this, mockContainerXBlockHtml);
304
                    fetch({published: true, has_changes: true, visibility_state: VisibilityState.needsAttention});
305 306

                    // Click discard changes
307
                    expect(containerPage.$(discardChangesButtonCss)).not.toHaveClass('is-disabled');
308 309 310 311 312
                    containerPage.$(discardChangesButtonCss).click();

                    // Click cancel to confirmation.
                    expect(promptSpies.constructor).toHaveBeenCalled();
                    promptSpies.constructor.mostRecentCall.args[0].actions.secondary.click(promptSpies);
313
                    AjaxHelpers.expectNoRequests(requests);
314 315
                    expect(containerPage.$(discardChangesButtonCss)).not.toHaveClass('is-disabled');
                });
316 317

                it('renders the last published date and user when there are no changes', function () {
318
                    renderContainerPage(this, mockContainerXBlockHtml);
319
                    fetch({published_on: "Jul 01, 2014 at 12:45 UTC", published_by: "amako"});
320 321 322 323 324
                    expect(containerPage.$(lastDraftCss).text()).
                        toContain("Last published Jul 01, 2014 at 12:45 UTC by amako");
                });

                it('renders the last saved date and user when there are changes', function () {
325
                    renderContainerPage(this, mockContainerXBlockHtml);
326
                    fetch({has_changes: true, edited_on: "Jul 02, 2014 at 14:20 UTC", edited_by: "joe"});
327 328 329
                    expect(containerPage.$(lastDraftCss).text()).
                        toContain("Draft saved on Jul 02, 2014 at 14:20 UTC by joe");
                });
330

331 332 333
                describe("Release Date", function() {
                    it('renders correctly when unreleased', function () {
                        renderContainerPage(this, mockContainerXBlockHtml);
334
                        fetch({
335
                            visibility_state: VisibilityState.ready, released_to_students: false,
336 337
                            release_date: "Jul 02, 2014 at 14:20 UTC", release_date_from: 'Section "Week 1"'
                        });
338
                        expect(containerPage.$(releaseDateTitleCss).text()).toContain("Scheduled:");
339 340
                        expect(containerPage.$(releaseDateDateCss).text()).toContain('Jul 02, 2014 at 14:20 UTC');
                        expect(containerPage.$(releaseDateWithCss).text()).toContain('with Section "Week 1"');
341 342 343 344
                    });

                    it('renders correctly when released', function () {
                        renderContainerPage(this, mockContainerXBlockHtml);
345
                        fetch({
346
                            visibility_state: VisibilityState.live, released_to_students: true,
347 348
                            release_date: "Jul 02, 2014 at 14:20 UTC", release_date_from: 'Section "Week 1"'
                        });
349
                        expect(containerPage.$(releaseDateTitleCss).text()).toContain("Released:");
350 351
                        expect(containerPage.$(releaseDateDateCss).text()).toContain('Jul 02, 2014 at 14:20 UTC');
                        expect(containerPage.$(releaseDateWithCss).text()).toContain('with Section "Week 1"');
352 353 354 355
                    });

                    it('renders correctly when the release date is not set', function () {
                        renderContainerPage(this, mockContainerXBlockHtml);
356
                        fetch({
357
                            visibility_state: VisibilityState.unscheduled, "released_to_students": false,
358 359
                            release_date: null, release_date_from: null
                        });
360 361 362 363 364 365
                        expect(containerPage.$(releaseDateTitleCss).text()).toContain("Release:");
                        expect(containerPage.$(releaseDateContentCss).text()).toContain("Unscheduled");
                    });

                    it('renders correctly when the unit is not published', function () {
                        renderContainerPage(this, mockContainerXBlockHtml);
366
                        fetch({
367
                            visibility_state: VisibilityState.needsAttention, released_to_students: true,
368 369
                            release_date: "Jul 02, 2014 at 14:20 UTC", release_date_from: 'Section "Week 1"'
                        });
370 371
                        containerPage.xblockPublisher.render();
                        expect(containerPage.$(releaseDateTitleCss).text()).toContain("Release:");
372 373
                        expect(containerPage.$(releaseDateDateCss).text()).toContain('Jul 02, 2014 at 14:20 UTC');
                        expect(containerPage.$(releaseDateWithCss).text()).toContain('with Section "Week 1"');
374
                    });
375 376
                });

377
                describe("Content Visibility", function () {
378
                    var requestStaffOnly, verifyStaffOnly, verifyExplicitStaffOnly, verifyImplicitStaffOnly, promptSpy,
379
                        visibilityTitleCss = '.wrapper-visibility .title';
380 381

                    requestStaffOnly = function(isStaffOnly) {
382 383
                        var newVisibilityState;

384 385
                        containerPage.$('.action-staff-lock').click();

386 387
                        // If removing explicit staff lock with no implicit staff lock, click 'Yes' to confirm
                        if (!isStaffOnly && !containerPage.model.get('ancestor_has_staff_lock')) {
388
                            EditHelpers.confirmPrompt(promptSpy);
389 390
                        }

391
                        AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/locator-container', {
392
                            publish: 'republish',
393
                            metadata: { visible_to_staff_only: isStaffOnly ? true : null }
394
                        });
395
                        AjaxHelpers.respondWithJson(requests, {
396 397 398
                            data: null,
                            id: "locator-container",
                            metadata: {
399
                                visible_to_staff_only: isStaffOnly ? true : null
400 401
                            }
                        });
402

403
                        AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
404 405 406 407 408
                        if (isStaffOnly || containerPage.model.get('ancestor_has_staff_lock')) {
                            newVisibilityState = VisibilityState.staffOnly;
                        } else {
                            newVisibilityState = VisibilityState.live;
                        }
409
                        AjaxHelpers.respondWithJson(requests, createXBlockInfo({
410
                            published: containerPage.model.get('published'),
411 412
                            has_explicit_staff_lock: isStaffOnly,
                            visibility_state: newVisibilityState,
413
                            release_date: "Jul 02, 2000 at 14:20 UTC"
414 415
                        }));
                    };
416

417
                    verifyStaffOnly = function(isStaffOnly) {
418
                        var visibilityCopy = containerPage.$('.wrapper-visibility .copy').text().trim();
419
                        if (isStaffOnly) {
420
                            expect(visibilityCopy).toContain('Staff Only');
421
                            expect(containerPage.$(bitPublishingCss)).toHaveClass(staffOnlyClass);
422
                        } else {
423
                            expect(visibilityCopy).toBe('Staff and Students');
424
                            expect(containerPage.$(bitPublishingCss)).not.toHaveClass(staffOnlyClass);
425 426 427 428 429 430 431
                            verifyExplicitStaffOnly(false);
                            verifyImplicitStaffOnly(false);
                        }
                    };

                    verifyExplicitStaffOnly = function(isStaffOnly) {
                        if (isStaffOnly) {
432
                            expect(containerPage.$('.action-staff-lock i')).toHaveClass('fa-check-square-o');
433
                        } else {
434
                            expect(containerPage.$('.action-staff-lock i')).toHaveClass('fa-square-o');
435 436 437 438 439 440 441 442
                        }
                    };

                    verifyImplicitStaffOnly = function(isStaffOnly) {
                        if (isStaffOnly) {
                            expect(containerPage.$('.wrapper-visibility .inherited-from')).toExist();
                        } else {
                            expect(containerPage.$('.wrapper-visibility .inherited-from')).not.toExist();
443 444
                        }
                    };
445

446 447 448 449 450
                    it("is initially shown to all", function() {
                        renderContainerPage(this, mockContainerXBlockHtml);
                        verifyStaffOnly(false);
                    });

451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
                    it("displays 'Is Visible To' when released and published", function() {
                        renderContainerPage(this, mockContainerXBlockHtml, {
                            released_to_students: true,
                            published: true,
                            has_changes: false
                        });
                        expect(containerPage.$(visibilityTitleCss).text()).toContain('Is Visible To');
                    });

                    it("displays 'Will Be Visible To' when not released or fully published", function() {
                        renderContainerPage(this, mockContainerXBlockHtml, {
                            released_to_students: false,
                            published: true,
                            has_changes: true
                        });
466
                        expect(containerPage.$(visibilityTitleCss).text()).toContain('Will Be Visible To');
467 468
                    });

469
                    it("can be explicitly set to staff only", function() {
470 471
                        renderContainerPage(this, mockContainerXBlockHtml);
                        requestStaffOnly(true);
472 473
                        verifyExplicitStaffOnly(true);
                        verifyImplicitStaffOnly(false);
474 475 476
                        verifyStaffOnly(true);
                    });

477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
                    it("can be implicitly set to staff only", function() {
                        renderContainerPage(this, mockContainerXBlockHtml, {
                            visibility_state: VisibilityState.staffOnly,
                            ancestor_has_staff_lock: true,
                            staff_lock_from: "Section Foo"
                        });
                        verifyImplicitStaffOnly(true);
                        verifyExplicitStaffOnly(false);
                        verifyStaffOnly(true);
                    });

                    it("can be explicitly and implicitly set to staff only", function() {
                        renderContainerPage(this, mockContainerXBlockHtml, {
                            visibility_state: VisibilityState.staffOnly,
                            ancestor_has_staff_lock: true,
                            staff_lock_from: "Section Foo"
                        });
                        requestStaffOnly(true);
                        // explicit staff lock overrides the display of implicit staff lock
                        verifyImplicitStaffOnly(false);
                        verifyExplicitStaffOnly(true);
                        verifyStaffOnly(true);
                    });

                    it("can remove explicit staff only setting without having implicit staff only", function() {
502
                        promptSpy = EditHelpers.createPromptSpy();
503
                        renderContainerPage(this, mockContainerXBlockHtml, {
504
                            visibility_state: VisibilityState.staffOnly,
505 506
                            has_explicit_staff_lock: true,
                            ancestor_has_staff_lock: false
507
                        });
508 509 510 511
                        requestStaffOnly(false);
                        verifyStaffOnly(false);
                    });

512
                    it("can remove explicit staff only setting while having implicit staff only", function() {
513
                        promptSpy = EditHelpers.createPromptSpy();
514 515 516 517 518 519 520 521 522 523 524 525
                        renderContainerPage(this, mockContainerXBlockHtml, {
                            visibility_state: VisibilityState.staffOnly,
                            ancestor_has_staff_lock: true,
                            has_explicit_staff_lock: true,
                            staff_lock_from: "Section Foo"
                        });
                        requestStaffOnly(false);
                        verifyExplicitStaffOnly(false);
                        verifyImplicitStaffOnly(true);
                        verifyStaffOnly(true);
                    });

526
                    it("does not refresh if removing staff only is canceled", function() {
527
                        promptSpy = EditHelpers.createPromptSpy();
528
                        renderContainerPage(this, mockContainerXBlockHtml, {
529
                            visibility_state: VisibilityState.staffOnly,
530 531
                            has_explicit_staff_lock: true,
                            ancestor_has_staff_lock: false
532
                        });
533
                        containerPage.$('.action-staff-lock').click();
534
                        EditHelpers.confirmPrompt(promptSpy, true);    // Click 'No' to cancel
535
                        AjaxHelpers.expectNoRequests(requests);
536
                        verifyExplicitStaffOnly(true);
537 538 539 540 541
                        verifyStaffOnly(true);
                    });

                    it("does not refresh when failing to set staff only", function() {
                        renderContainerPage(this, mockContainerXBlockHtml);
542
                        containerPage.$('.action-staff-lock').click();
543
                        AjaxHelpers.respondWithError(requests);
544
                        AjaxHelpers.expectNoRequests(requests);
545 546
                        verifyStaffOnly(false);
                    });
547
                });
548 549 550 551 552
            });

            describe("PublishHistory", function () {
                var lastPublishCss = ".wrapper-last-publish";

553 554 555 556 557 558 559
                it('renders never published when the block is unpublished', function () {
                    renderContainerPage(this, mockContainerXBlockHtml, {
                        published: false, published_on: null, published_by: null
                    });
                    expect(containerPage.$(lastPublishCss).text()).toContain("Never published");
                });

560 561 562
                it('renders the last published date and user when the block is published', function() {
                    renderContainerPage(this, mockContainerXBlockHtml);
                    fetch({
563
                        published: true, published_on: "Jul 01, 2014 at 12:45 UTC", published_by: "amako"
564
                    });
565 566 567 568 569
                    expect(containerPage.$(lastPublishCss).text()).
                        toContain("Last published Jul 01, 2014 at 12:45 UTC by amako");
                });

                it('renders correctly when the block is published without publish info', function () {
570 571
                    renderContainerPage(this, mockContainerXBlockHtml);
                    fetch({
572
                        published: true, published_on: null, published_by: null
573
                    });
574 575
                    expect(containerPage.$(lastPublishCss).text()).toContain("Previously published");
                });
576
            });
577 578 579

            describe("Message Area", function() {
                var messageSelector = '.container-message .warning',
580 581
                    warningMessage = 'Caution: The last published version of this unit is live. ' +
                        'By publishing changes you will change the student experience.';
582 583 584

                it('is empty for a unit that is not currently visible to students', function() {
                    renderContainerPage(this, mockContainerXBlockHtml, {
585
                        currently_visible_to_students: false
586 587 588 589
                    });
                    expect(containerPage.$(messageSelector).text().trim()).toBe('');
                });

590
                it('shows a message for a unit that is currently visible to students', function() {
591
                    renderContainerPage(this, mockContainerXBlockHtml, {
592
                        currently_visible_to_students: true
593
                    });
594
                    expect(containerPage.$(messageSelector).text().trim()).toBe(warningMessage);
595 596 597 598
                });

                it('hides the message when the unit is hidden from students', function() {
                    renderContainerPage(this, mockContainerXBlockHtml, {
599
                        currently_visible_to_students: true
600 601 602 603 604 605 606
                    });
                    fetch({ currently_visible_to_students: false });
                    expect(containerPage.$(messageSelector).text().trim()).toBe('');
                });

                it('shows a message when a unit is made visible', function() {
                    renderContainerPage(this, mockContainerXBlockHtml, {
607
                        currently_visible_to_students: false
608 609
                    });
                    fetch({ currently_visible_to_students: true });
610
                    expect(containerPage.$(messageSelector).text().trim()).toBe(warningMessage);
611 612
                });
            });
613 614
        });
    });