Skip to content

Commit 5d5318f

Browse files
authored
Add duplicate modules conflict (#3738)
* Add duplicate modules conflict Duplicate conflict checks if there are modules with the same module code added to the same academic year and semester. * Add duplicate conflict message * Format a line
1 parent e6423d8 commit 5d5318f

File tree

4 files changed

+126
-1
lines changed

4 files changed

+126
-1
lines changed

website/src/selectors/planner.test.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { State } from 'types/state';
1010
const CS1010X = clone(CS1010S);
1111
CS1010X.moduleCode = 'CS1010X';
1212

13+
const CS3216_DUPLICATE = clone(CS3216);
14+
const CS1010S_DUPLICATE = clone(CS1010S);
15+
1316
/* eslint-disable no-useless-computed-key */
1417

1518
const defaultState: PlannerState = {
@@ -225,6 +228,98 @@ describe(getAcadYearModules, () => {
225228
]);
226229
});
227230

231+
test('should return duplicate conflicts', () => {
232+
const planner: PlannerState = {
233+
...defaultState,
234+
modules: {
235+
0: { id: '0', moduleCode: 'CS3216', year: '2018/2019', semester: 1, index: 0 },
236+
1: { id: '1', moduleCode: 'CS3216', year: '2018/2019', semester: 1, index: 1 },
237+
2: { id: '2', moduleCode: 'CS1010S', year: '2018/2019', semester: 2, index: 0 },
238+
3: { id: '3', moduleCode: 'CS1010S', year: '2018/2019', semester: 2, index: 1 },
239+
},
240+
};
241+
242+
const moduleBank = {
243+
modules: { CS3216, CS3216_DUPLICATE, CS1010S, CS1010S_DUPLICATE },
244+
moduleCodes: {
245+
CS3216: { semesters: [1] },
246+
CS3216_clone: { semesters: [1] },
247+
CS1010S: { semesters: [2] },
248+
CS1010S_clone: { semesters: [2] },
249+
},
250+
};
251+
252+
const state: any = { planner, moduleBank };
253+
254+
expect(getAcadYearModules(state)).toHaveProperty('2018/2019.1', [
255+
{
256+
id: '0',
257+
moduleCode: 'CS3216',
258+
moduleInfo: CS3216,
259+
conflict: { type: 'duplicate' },
260+
},
261+
{
262+
id: '1',
263+
moduleCode: 'CS3216',
264+
moduleInfo: CS3216_DUPLICATE,
265+
conflict: { type: 'duplicate' },
266+
},
267+
]);
268+
269+
expect(getAcadYearModules(state)).toHaveProperty('2018/2019.2', [
270+
{
271+
id: '2',
272+
moduleCode: 'CS1010S',
273+
moduleInfo: CS1010S,
274+
conflict: { type: 'duplicate' },
275+
},
276+
{
277+
id: '3',
278+
moduleCode: 'CS1010S',
279+
moduleInfo: CS1010S_DUPLICATE,
280+
conflict: { type: 'duplicate' },
281+
},
282+
]);
283+
});
284+
285+
test('should not have duplicate conflicts for same modules in different semesters', () => {
286+
const planner: PlannerState = {
287+
...defaultState,
288+
modules: {
289+
0: { id: '0', moduleCode: 'CS1010S', year: '2018/2019', semester: 1, index: 0 },
290+
1: { id: '1', moduleCode: 'CS1010S', year: '2018/2019', semester: 2, index: 0 },
291+
},
292+
};
293+
294+
const moduleBank = {
295+
modules: { CS1010S, CS1010S_DUPLICATE },
296+
moduleCodes: {
297+
CS1010S: { semesters: [1, 2] },
298+
CS1010S_clone: { semesters: [1, 2] },
299+
},
300+
};
301+
302+
const state: any = { planner, moduleBank };
303+
304+
expect(getAcadYearModules(state)).toHaveProperty('2018/2019.1', [
305+
{
306+
id: '0',
307+
moduleCode: 'CS1010S',
308+
moduleInfo: CS1010S,
309+
conflict: null,
310+
},
311+
]);
312+
313+
expect(getAcadYearModules(state)).toHaveProperty('2018/2019.2', [
314+
{
315+
id: '1',
316+
moduleCode: 'CS1010S',
317+
moduleInfo: CS1010S_DUPLICATE,
318+
conflict: null,
319+
},
320+
]);
321+
});
322+
228323
test('should not show exam conflicts for modules not taken this year', () => {
229324
const planner: PlannerState = {
230325
...defaultState,

website/src/selectors/planner.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ const examConflict =
105105
return null;
106106
};
107107

108+
/**
109+
* Checks if there are duplicate modules in one semester
110+
*/
111+
const duplicateConflict =
112+
(moduleCodeMap: PlannerTime[]) =>
113+
(moduleCode: ModuleCode): Conflict | null =>
114+
moduleCodeMap.filter((module) => module.moduleCode === moduleCode).length === 1
115+
? null
116+
: { type: 'duplicate' };
117+
108118
function mapModuleToInfo(
109119
module: PlannerTime,
110120
modulesMap: ModulesMap,
@@ -224,6 +234,7 @@ export function getAcadYearModules(state: State): PlannerModulesWithInfo {
224234
noInfoConflict(moduleBank.moduleCodes, planner.custom),
225235
semesterConflict(moduleBank.moduleCodes, semester),
226236
examConflict(clashes),
237+
duplicateConflict(moduleTimes),
227238
];
228239

229240
if (!planner.ignorePrereqCheck) {

website/src/types/planner.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,16 @@ export type NoInfo = {
3030
type: 'noInfo';
3131
};
3232

33-
export type Conflict = PrereqConflict | ExamConflict | SemesterConflict | NoInfo;
33+
export type DuplicateConflict = {
34+
type: 'duplicate';
35+
};
36+
37+
export type Conflict =
38+
| PrereqConflict
39+
| ExamConflict
40+
| SemesterConflict
41+
| NoInfo
42+
| DuplicateConflict;
3443

3544
export type PlannerModulesWithInfo = {
3645
// Mapping acad years to a map of semester to module information object

website/src/views/planner/PlannerModule.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ const PlannerModule = memo<Props>((props) => {
104104
</>
105105
);
106106

107+
case 'duplicate':
108+
return (
109+
<>
110+
<div className={styles.conflictHeader}>
111+
<AlertTriangle className={styles.warningIcon} />
112+
<p>This might be a duplicate of another course in this semester.</p>
113+
</div>
114+
</>
115+
);
116+
107117
default:
108118
return null;
109119
}

0 commit comments

Comments
 (0)