feat: handle courseware paths more liberally (#395)
Valid courseware URLs currently include: * /course/:courseId * /course/:courseId/:sequenceId * /course/:courseId/:sequenceId/:unitId In this commit we add support for: * /course/:courseId/:sectionId * /course/:courseId/:sectionId/:unitId * /course/:courseId/:unitId All URL forms still redirect to: /course/:courseId/:sequenceId/:unitId See ADR #8 for more context. All changes: * refactor: allow courseBlocks factory to build multiple sections * refactor: make CoursewareContainer tests less brittle & stateful * feat: handle courseware paths more liberally * refactor: reorder, rename, & comment redirection functions TNL-7796
This commit is contained in:
@@ -75,8 +75,8 @@ export default function buildSimpleCourseAndSequenceMetadata(options = {}) {
|
||||
});
|
||||
const courseId = courseMetadata.id;
|
||||
const simpleCourseBlocks = buildSimpleCourseBlocks(courseId, courseMetadata.name, options);
|
||||
const { unitBlocks, sequenceBlock } = simpleCourseBlocks;
|
||||
const sequenceMetadata = options.sequenceMetadata || sequenceBlock.map(block => Factory.build(
|
||||
const { unitBlocks, sequenceBlocks } = simpleCourseBlocks;
|
||||
const sequenceMetadata = options.sequenceMetadata || sequenceBlocks.map(block => Factory.build(
|
||||
'sequenceMetadata',
|
||||
{},
|
||||
{
|
||||
|
||||
@@ -164,6 +164,7 @@ function normalizeSequenceMetadata(sequence) {
|
||||
return {
|
||||
sequence: {
|
||||
id: sequence.item_id,
|
||||
blockType: sequence.tag,
|
||||
unitIds: sequence.items.map(unit => unit.id),
|
||||
bannerText: sequence.banner_text,
|
||||
format: sequence.format,
|
||||
|
||||
@@ -24,18 +24,18 @@ describe('Data layer integration tests', () => {
|
||||
// building minimum set of api responses to test all thunks
|
||||
const courseMetadata = Factory.build('courseMetadata');
|
||||
const courseId = courseMetadata.id;
|
||||
const { courseBlocks, unitBlocks, sequenceBlock } = buildSimpleCourseBlocks(courseId);
|
||||
const { courseBlocks, unitBlocks, sequenceBlocks } = buildSimpleCourseBlocks(courseId);
|
||||
const sequenceMetadata = Factory.build(
|
||||
'sequenceMetadata',
|
||||
{},
|
||||
{ courseId, unitBlocks, sequenceBlock: sequenceBlock[0] },
|
||||
{ courseId, unitBlocks, sequenceBlock: sequenceBlocks[0] },
|
||||
);
|
||||
|
||||
let courseUrl = `${courseBaseUrl}/${courseId}`;
|
||||
courseUrl = appendBrowserTimezoneToUrl(courseUrl);
|
||||
|
||||
const sequenceUrl = `${sequenceBaseUrl}/${sequenceMetadata.item_id}`;
|
||||
const sequenceId = sequenceBlock[0].id;
|
||||
const sequenceId = sequenceBlocks[0].id;
|
||||
const unitId = unitBlocks[0].id;
|
||||
|
||||
let store;
|
||||
@@ -115,6 +115,22 @@ describe('Data layer integration tests', () => {
|
||||
expect(store.getState().courseware.sequenceStatus).toEqual('failed');
|
||||
});
|
||||
|
||||
it('Should result in fetch failure if a non-sequential block is returned', async () => {
|
||||
const sectionMetadata = {
|
||||
...sequenceMetadata,
|
||||
// 'chapter' is the block_type of a Section, which the sequence metadata
|
||||
// API will happily return if requested, since SectionBlock is implemented
|
||||
// as a subclass of SequenceBlock.
|
||||
tag: 'chapter',
|
||||
};
|
||||
axiosMock.onGet(sequenceUrl).reply(200, sectionMetadata);
|
||||
|
||||
await executeThunk(thunks.fetchSequence(sequenceId), store.dispatch);
|
||||
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(store.getState().courseware.sequenceStatus).toEqual('failed');
|
||||
});
|
||||
|
||||
it('Should fetch and normalize metadata, and then update existing models with sequence metadata', async () => {
|
||||
axiosMock.onGet(courseUrl).reply(200, courseMetadata);
|
||||
axiosMock.onGet(courseBlocksUrlRegExp).reply(200, courseBlocks);
|
||||
|
||||
@@ -93,15 +93,26 @@ export function fetchSequence(sequenceId) {
|
||||
dispatch(fetchSequenceRequest({ sequenceId }));
|
||||
try {
|
||||
const { sequence, units } = await getSequenceMetadata(sequenceId);
|
||||
dispatch(updateModel({
|
||||
modelType: 'sequences',
|
||||
model: sequence,
|
||||
}));
|
||||
dispatch(updateModels({
|
||||
modelType: 'units',
|
||||
models: units,
|
||||
}));
|
||||
dispatch(fetchSequenceSuccess({ sequenceId }));
|
||||
if (sequence.blockType !== 'sequential') {
|
||||
// Some other block types (particularly 'chapter') can be returned
|
||||
// by this API. We want to error in that case, since downstream
|
||||
// courseware code is written to render Sequences of Units.
|
||||
logError(
|
||||
`Requested sequence '${sequenceId}' `
|
||||
+ `has block type '${sequence.blockType}'; expected block type 'sequential'.`,
|
||||
);
|
||||
dispatch(fetchSequenceFailure({ sequenceId }));
|
||||
} else {
|
||||
dispatch(updateModel({
|
||||
modelType: 'sequences',
|
||||
model: sequence,
|
||||
}));
|
||||
dispatch(updateModels({
|
||||
modelType: 'units',
|
||||
models: units,
|
||||
}));
|
||||
dispatch(fetchSequenceSuccess({ sequenceId }));
|
||||
}
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
dispatch(fetchSequenceFailure({ sequenceId }));
|
||||
|
||||
Reference in New Issue
Block a user