Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issues/KUI-1176 syllabus bugfix #345

Merged
merged 2 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions public/js/app/components/CourseSectionList.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import React from 'react'

import { useWebContext } from '../context/WebContext'

import { useLanguage } from '../hooks/useLanguage'
import { useMissingInfo } from '../hooks/useMissingInfo'
import CourseSection from './CourseSections'
import SyllabusInformation from './SyllabusInformation'
import { SyllabusInformation } from './SyllabusInformation'

function CourseSectionList({ courseInfo = {}, partToShow, syllabus = {}, syllabusName, hasSyllabus }) {
const context = useWebContext()
function CourseSectionList({ courseInfo = {}, partToShow, syllabus = {}, syllabusName }) {
const { translation } = useLanguage()
const { isMissingInfoLabel, missingInfoLabel } = useMissingInfo()

Expand Down Expand Up @@ -187,7 +184,7 @@ function CourseSectionList({ courseInfo = {}, partToShow, syllabus = {}, syllabu
id={partToShow}
aria-label={`${translation.courseLabels.label_course_information} ${syllabusName}`}
>
<SyllabusInformation context={context} syllabus={syllabus} hasSyllabus={hasSyllabus} translation={translation} />
<SyllabusInformation syllabusName={syllabusName} />
<CourseSection
sectionHeader={translation.courseLabels.header_content}
headerType="3"
Expand Down
38 changes: 17 additions & 21 deletions public/js/app/components/SyllabusInformation.jsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
import React from 'react'
import { Col } from 'reactstrap'
import { FaAsterisk } from 'react-icons/fa'
import { useLanguage } from '../hooks/useLanguage'

const SyllabusInformation = ({ context = {}, syllabus = {}, hasSyllabus, translation = {} }) => {
const { courseCode } = context
const ActualSyllabusInformation = ({ syllabusName }) => {
const { translation } = useLanguage()

const { courseLabels, courseInformation } = translation
const {
label_course_syllabus_source: labelCourseSyllabusSource,
label_course_syllabus_denoted: labelCourseSyllabusDenoted,
syllabus_marker_aria_label: labelCourseSyllabusAsterisk,
} = courseLabels
const { course_short_semester: courseShortSemester } = courseInformation

const { course_valid_from: courseValidFrom = ['', ''], course_valid_to: courseValidTo = ['', ''] } = syllabus
const courseValidFromLabel = `${courseShortSemester[courseValidFrom[1]]}${courseValidFrom[0]}`
const courseValidToLabel = courseValidTo.length ? courseShortSemester[courseValidTo[1]] + '' + courseValidTo[0] : ''
const courseValidRangeLabel = `(${courseValidFromLabel}\u2013${courseValidToLabel})`

const syllabusText = `${labelCourseSyllabusSource} ${courseCode} ${courseValidRangeLabel} ${labelCourseSyllabusDenoted}`
} = translation.courseLabels

return (
<Col sm="12">
<div>
{hasSyllabus && (
<span>
{syllabusText}
{' ( '}
<FaAsterisk className="syllabus-marker-icon-small" aria-label={labelCourseSyllabusAsterisk} />
{' )'}
</span>
)}
<span>
{`${labelCourseSyllabusSource} ${syllabusName} ${labelCourseSyllabusDenoted}`}
{' ( '}
<FaAsterisk className="syllabus-marker-icon-small" aria-label={labelCourseSyllabusAsterisk} />
{' )'}
</span>
</div>
</Col>
)
}

export default SyllabusInformation
const SyllabusInformationOrNull = ({ syllabusName }) => {
if (!syllabusName) return null

return <ActualSyllabusInformation syllabusName={syllabusName} />
}

export { SyllabusInformationOrNull as SyllabusInformation }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious what you think about this solution
I thought that calling the component SyllabusInformationOrNull for SyllabusInformation would be misleading. Rather the component ActualSyllabusInformation should be called SyllabusInformation, but then we would have a mismatch in file name <-> exported component.

Note also the change to named export.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First: I like the change to named export. What are the real advantages of using default export? Or why don't we always use named exports?

I'm not sure I think calling SyllabusInformationOrNull for SyllabusInformation would be misleading actually. For the "user" SyllabusInformation is what the component actually is. The null check with an extra component is just an internal behaviour the "user" doesn't need to know about ("user"=the other component using this component).

And with that said, the only like important thing is the name of the actual exported component.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious about the approach here.

Why can’t the functional component ActualSyllabusInformation just be called SyllabusInformation and either just return the html
if syllabusName exists or 
return (null) if syllabusName does not exist? Is it not possible to get rid of SyllabusInformationOrNull altogether?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good question!
This is certainly possible. This is a rather philosophical discussion about readability and the notion that "one function should do one thing", which - I think - is mentioned in Clean Code in the chapter about functions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree with Karl and think the approach that you have used is indeed the cleanest approach that could be used here.
right now there is nothing that mislead other developers. I also really liked the aliasing approach for exporting the component.
I used to handle this functionality (checking to show the component if is not null) inline in the other components and now I think this approach can help me to have cleaner code.
I'll also check the mentioned part inside the clean code book which is valuable to read.

18 changes: 15 additions & 3 deletions public/js/app/components/__tests__/CourseSectionList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,26 @@ describe('Component <CourseSectionList>', () => {
const courseInfoWithLiterature = { course_literature: courseLiteratureTitle }

const [syllabusLiteratureNoTitle] = INFORM_IF_IMPORTANT_INFO_IS_MISSING // en
const syllabusWithoutLiterature = { course_literature: `<i>${syllabusLiteratureNoTitle}</i>` }
const syllabusWithoutLiterature = {
course_valid_from: { year: 2020, semesterNumber: 2 },
course_literature: `<i>${syllabusLiteratureNoTitle}</i>`,
}

const syllabusLiteratureTitle = 'Syllabus Literature (1970)'
const syllabusLiteratureComment = 'Please read this book.'
const [syllabusLiteratureNoComment] = INFORM_IF_IMPORTANT_INFO_IS_MISSING // en
const syllabusWithLiteratureAndComment = {
course_valid_from: { year: 2020, semesterNumber: 2 },
course_literature: syllabusLiteratureTitle,
course_literature_comment: syllabusLiteratureComment,
}
const syllabusWithLiteratureNoComment = {
course_valid_from: { year: 2020, semesterNumber: 2 },
course_literature: syllabusLiteratureTitle,
course_literature_comment: `<i>${syllabusLiteratureNoComment}</i>`,
}
const syllabusWithNoLiteratureAndComment = {
course_valid_from: { year: 2020, semesterNumber: 2 },
course_literature_comment: syllabusLiteratureComment,
}

Expand Down Expand Up @@ -100,7 +106,10 @@ describe('Component <CourseSectionList>', () => {
}
const { rerender } = render(
<WebContextProvider configIn={{ courseInfo: courseInfoWithLink }}>
<CourseSectionList courseInfo={courseInfoWithLink} />
<CourseSectionList
courseInfo={courseInfoWithLink}
syllabus={{ course_valid_from: { year: 2020, semesterNumber: 2 } }}
/>
</WebContextProvider>
)
const linkText1 = screen.queryByText(courseDepartmentLinkText)
Expand All @@ -114,7 +123,10 @@ describe('Component <CourseSectionList>', () => {
}
rerender(
<WebContextProvider configIn={{ courseInfo: courseInfoWithoutLink }}>
<CourseSectionList courseInfo={courseInfoWithoutLink} />
<CourseSectionList
courseInfo={courseInfoWithoutLink}
syllabus={{ course_valid_from: { year: 2020, semesterNumber: 2 } }}
/>
</WebContextProvider>
)
const linkText2 = screen.queryByText(courseDepartmentLinkText)
Expand Down
39 changes: 29 additions & 10 deletions public/js/app/components/__tests__/SyllabusInformation.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
import React from 'react'
import { render } from '@testing-library/react'
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'

import SyllabusInformation from '../SyllabusInformation'
import i18n from '../../../../../i18n'

const [translationEN, translationSV] = i18n.messages
import { SyllabusInformation } from '../SyllabusInformation'
import { WebContextProvider } from '../../context/WebContext'

describe('Component <SyllabusInformation>', () => {
test('renders syllabus information in English', () => {
render(<SyllabusInformation translation={translationEN} />)
})
test('renders syllabus information in Swedish', () => {
render(<SyllabusInformation translation={translationSV} />)
test.each(['AF1745 (Spring 2023–)', 'SH2020 (Autumn 2022-Spring 2024'])(
`renders syllabus information if SyllabusName "%s" is given`,
syllabusName => {
render(
<WebContextProvider configIn={{ lang: 'en' }}>
<SyllabusInformation syllabusName={syllabusName} />
</WebContextProvider>
)

expect(
screen.getByText(
`Headings with content from the Course syllabus ${syllabusName} are denoted with an asterisk ( )`
)
).toBeInTheDocument()
}
)

test('does not render syllabus information if no syllabusName is given', () => {
render(
<WebContextProvider configIn={{ lang: 'en' }}>
<SyllabusInformation syllabusName={''} />
</WebContextProvider>
)

expect(screen.queryByText(`Headings with content from the Course syllabus`)).not.toBeInTheDocument()
})
})
14 changes: 14 additions & 0 deletions public/js/app/hooks/__tests__/useSemesterRoundState.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,20 @@ describe('useSemesterRoundsLogic', () => {
expect(result.current.hasSyllabus).toBe(false)
})

test('if selectedSemester is undefined, sets the latest syllabus as activeSyllabus', () => {
const { result } = renderHook(() =>
useSemesterRoundState({
initiallySelectedRoundIndex: undefined,
initiallySelectedSemester: undefined,
roundsBySemester,
syllabusList,
activeSemesters,
})
)

expect(result.current.activeSyllabus).toEqual(syllabusList[0])
})

test('calling resetSelectedRoundIndex resets round values', async () => {
const { result } = renderHook(() =>
useSemesterRoundState({
Expand Down
9 changes: 5 additions & 4 deletions public/js/app/hooks/useSemesterRoundState.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ const useSemesterRoundState = ({

const hasActiveSemesters = useMemo(() => activeSemesters && activeSemesters.length > 0, [activeSemesters])

const activeSyllabus = useMemo(
() => getValidSyllabusForSemester(syllabusList, selectedSemester),
[syllabusList, selectedSemester]
)
const activeSyllabus = useMemo(() => {
if (!selectedSemester) return getElementOrEmpty(syllabusList, 0)

return getValidSyllabusForSemester(syllabusList, selectedSemester)
}, [syllabusList, selectedSemester])

const hasSyllabus = useMemo(
() => syllabusList && syllabusList.length > 0 && activeSyllabus !== undefined,
Expand Down
49 changes: 20 additions & 29 deletions public/js/app/pages/CoursePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ function CoursePage() {
return () => (isMounted = false)
})

const createValidToString = () => {
if (!activeSyllabus.course_valid_to) return ''
return `${translation.courseInformation.course_short_semester[activeSyllabus.course_valid_to.semesterNumber]}${activeSyllabus.course_valid_to.year}`
}

const createValidFromString = () =>
`${
translation.courseInformation.course_short_semester[activeSyllabus.course_valid_from.semesterNumber]
}${activeSyllabus.course_valid_from.year}`

const createSyllabusName = () => {
if (!hasSyllabus) return ''
return `${courseCode}${` (${createValidFromString()}\u2013${createValidToString()})`}`
}

const syllabusName = createSyllabusName()

return (
<Row id="kursinfo-main-page">
<SideMenu courseCode={courseCode} labels={translation.courseLabels.sideMenu} />
Expand Down Expand Up @@ -262,7 +279,7 @@ function CoursePage() {
aria-label={translation.courseLabels.label_syllabus_pdf_header}
>
<h3 className="t4">{translation.courseLabels.label_syllabus_pdf_header}</h3>
{activeSyllabus && activeSyllabus.course_valid_from && activeSyllabus.course_valid_from.year ? (
{hasSyllabus && activeSyllabus.course_valid_from && activeSyllabus.course_valid_from.year ? (
<>
<p>{translation.courseLabels.label_syllabus_pdf_info}</p>
<a
Expand All @@ -272,17 +289,7 @@ function CoursePage() {
rel="noreferrer"
className="pdf-link pdf-link-fix pdf-link-last-line"
>
{`${translation.courseLabels.label_syllabus_link} ${courseCode}${` (${
translation.courseInformation.course_short_semester[
activeSyllabus.course_valid_from.semesterNumber
]
}${activeSyllabus.course_valid_from.year}–${
activeSyllabus.course_valid_to.length > 0
? translation.courseInformation.course_short_semester[activeSyllabus.course_valid_to[1]] +
'' +
activeSyllabus.course_valid_to[0]
: ''
})`}`}
{`${translation.courseLabels.label_syllabus_link} ${syllabusName}`}
</a>
</>
) : (
Expand Down Expand Up @@ -313,23 +320,7 @@ function CoursePage() {
syllabus={activeSyllabus}
hasSyllabus={hasSyllabus}
partToShow="courseContentBlock"
syllabusName={
hasSyllabus
? `${courseCode}${` (${
translation.courseInformation.course_short_semester[
activeSyllabus.course_valid_from.semesterNumber
]
}${
activeSyllabus && activeSyllabus.course_valid_from ? activeSyllabus.course_valid_from.year : ''
}–${
activeSyllabus.course_valid_to.length > 0
? translation.courseInformation.course_short_semester[activeSyllabus.course_valid_to[1]] +
'' +
activeSyllabus.course_valid_to[0]
: ''
})`}`
: ''
}
syllabusName={hasSyllabus ? syllabusName : ''}
/>

{/* ---IF RESEARCH LEVEL: SHOW "Postgraduate course" LINK-- */}
Expand Down
6 changes: 4 additions & 2 deletions public/js/app/pages/__tests__/CoursePage.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/controllers/__tests__/courseCtrl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ describe('Discontinued course to test', () => {
"semesterNumber": 2,
"year": 2019,
},
"course_valid_to": [],
"course_valid_to": undefined,
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/__tests__/createSyllabusList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const expectedSyllabusList = [
course_literature_comment:
'<p>Announced no later than 4 weeks before the start of the course on the course web page.</p>',
course_valid_from: { year: 2019, semesterNumber: 2 },
course_valid_to: [],
course_valid_to: undefined,
course_required_equipment: '<i>No information inserted</i>',
course_examination:
"<ul class='ul-no-padding' ><li>TEN1 - \n Examination,\n 7.5 credits, \n grading scale: A, B, C, D, E, FX, F \n </li></ul>",
Expand Down
6 changes: 3 additions & 3 deletions server/controllers/createSyllabusList.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ const _createEmptySyllabusData = language => ({
course_requirments_for_final_grade: '',
course_literature: INFORM_IF_IMPORTANT_INFO_IS_MISSING[language],
course_literature_comment: INFORM_IF_IMPORTANT_INFO_IS_MISSING[language],
course_valid_from: [],
course_valid_to: [],
course_valid_from: undefined,
course_valid_to: undefined,
course_required_equipment: '',
course_examination: INFORM_IF_IMPORTANT_INFO_IS_MISSING[language],
course_examination_comments: '',
Expand Down Expand Up @@ -75,7 +75,7 @@ const _parseSyllabusData = (courseDetails, semesterIndex = 0, language) => {
course_literature: parseOrSetEmpty(semesterSyllabus.courseSyllabus.literature, language),
course_literature_comment: parseOrSetEmpty(semesterSyllabus.courseSyllabus.literatureComment, language),
course_valid_from: parseSemesterIntoYearSemesterNumber(parseOrSetEmpty(semesterSyllabus.validFromTerm.term)),
course_valid_to: [],
course_valid_to: undefined,
course_required_equipment: parseOrSetEmpty(semesterSyllabus.courseSyllabus.requiredEquipment, language),
course_examination:
examinationSets && Object.keys(examinationSets).length > 0
Expand Down
9 changes: 9 additions & 0 deletions server/util/semesterUtils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/**
* semester: 20242 (Number) -> Autumn 2024
* yearSemesterNubmer: {
* year: number
* semesterNumber: number
* }
*
*/

/**
* Takes a yearSemesterNumber and returns a yearSemesterNumber representing the semester previous to the given semester
*
Expand Down
Loading