diff --git a/web/app/components-v2/opiskeluoikeus/KoodistoField.tsx b/web/app/components-v2/opiskeluoikeus/KoodistoField.tsx index f6f78b7220..7cad322bc3 100644 --- a/web/app/components-v2/opiskeluoikeus/KoodistoField.tsx +++ b/web/app/components-v2/opiskeluoikeus/KoodistoField.tsx @@ -3,7 +3,7 @@ import { t } from '../../i18n/i18n' import { Koodistokoodiviite } from '../../types/fi/oph/koski/schema/Koodistokoodiviite' import { CommonProps, common } from '../CommonProps' import { FieldEditorProps, FieldViewerProps } from '../forms/FormField' -import { TestIdLayer } from '../../appstate/useTestId' +import { TestIdLayer, useTestId } from '../../appstate/useTestId' import { Select, SelectOption, @@ -24,11 +24,14 @@ export type KoodistoViewProps = CommonProps< > > -export const KoodistoView = (props: KoodistoViewProps) => ( -
- {t(props.value?.nimi) || '–'} -
-) +export const KoodistoView = (props: KoodistoViewProps) => { + const testId = useTestId(props.testId) + return ( +
+ {t(props.value?.nimi) || '–'} +
+ ) +} export type KoodistoEditProps = CommonProps< FieldEditorProps< diff --git a/web/app/components-v2/opiskeluoikeus/OpiskeluoikeudenTila.tsx b/web/app/components-v2/opiskeluoikeus/OpiskeluoikeudenTila.tsx index d7b1c3f061..3e920e186b 100644 --- a/web/app/components-v2/opiskeluoikeus/OpiskeluoikeudenTila.tsx +++ b/web/app/components-v2/opiskeluoikeus/OpiskeluoikeudenTila.tsx @@ -83,13 +83,16 @@ export const OpiskeluoikeudenTilaView = ( , - - {t(jakso.tila.nimi)} - - {isRahoituksellinenOpiskeluoikeusjakso(jakso) && - ` (${t(jakso.opintojenRahoitus?.nimi)})`} - - + {t(jakso.tila.nimi)} + {isRahoituksellinenOpiskeluoikeusjakso(jakso) && ( + <> + {' ('} + + {t(jakso.opintojenRahoitus?.nimi)} + + {')'} + + )} ]} diff --git a/web/app/components-v2/opiskeluoikeus/OppiaineTable.tsx b/web/app/components-v2/opiskeluoikeus/OppiaineTable.tsx index ee50f9ea69..039206ba20 100644 --- a/web/app/components-v2/opiskeluoikeus/OppiaineTable.tsx +++ b/web/app/components-v2/opiskeluoikeus/OppiaineTable.tsx @@ -3,16 +3,14 @@ import { constant, pipe } from 'fp-ts/lib/function' import * as NEA from 'fp-ts/NonEmptyArray' import * as NonEmptyArray from 'fp-ts/NonEmptyArray' import * as O from 'fp-ts/Option' +import * as Ord from 'fp-ts/Ord' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { ISO2FinnishDate } from '../../date/date' import { t } from '../../i18n/i18n' import { isArvioinniton } from '../../types/fi/oph/koski/schema/Arvioinniton' import { Arviointi } from '../../types/fi/oph/koski/schema/Arviointi' import { IBOpiskeluoikeus } from '../../types/fi/oph/koski/schema/IBOpiskeluoikeus' -import { - IBOppiaineenSuoritus, - isIBOppiaineenSuoritus -} from '../../types/fi/oph/koski/schema/IBOppiaineenSuoritus' +import { isIBOppiaineenSuoritus } from '../../types/fi/oph/koski/schema/IBOppiaineenSuoritus' import { IBTheoryOfKnowledgeSuoritus } from '../../types/fi/oph/koski/schema/IBTheoryOfKnowledgeSuoritus' import { IBTutkinnonSuoritus } from '../../types/fi/oph/koski/schema/IBTutkinnonSuoritus' import { LukionArviointi } from '../../types/fi/oph/koski/schema/LukionArviointi' @@ -40,6 +38,9 @@ import { FormModel, getValue } from '../forms/FormModel' import { CHARCODE_REMOVE } from '../texts/Icon' import { ArvosanaEdit, koodiarvoOnly } from './ArvosanaField' import { OppiaineTableKurssiEditor } from './OppiaineTableKurssiEditor' +import { TestIdLayer, TestIdText } from '../../appstate/useTestId' +import { string } from 'fp-ts' +import { isPaikallinenKoodi } from '../../types/fi/oph/koski/schema/PaikallinenKoodi' // Vain OppiaineTablen tukemat päätason suoritukset (tätä komponenttia tullaan myöhemmin käyttämään ainakin lukion näkymille) export type OppiaineTableOpiskeluoikeus = IBOpiskeluoikeus @@ -150,36 +151,46 @@ export const OppiaineTable = ({ {form.editMode && } - {groupedOppiaineet.map(([groupName, oppiaineet], groupIndex) => ( - - {groupName && ( - - {groupName} - - )} - {oppiaineet.map((oppiaine, oppiaineIndex) => ( - - ))} - - ))} + + {groupedOppiaineet.map(([groupName, oppiaineet], groupIndex) => ( + + + {groupName ? ( + + + {groupName} + + + ) : null} + + {oppiaineet.map((oppiaine, oppiaineIndex) => ( + + + + ))} + + + + ))} + ) } @@ -247,7 +258,9 @@ const OppiaineRow = ({
- {oppiaineenNimi(oppiaine.koulutusmoduuli)} + + {oppiaineenNimi(oppiaine.koulutusmoduuli)} +
({ onShowAddOsasuoritusDialog={showAddOsasuoritusDialog} /> - {kurssejaYhteensä} + + {kurssejaYhteensä} + {showPredictedGrade && ( void } +const kurssiNaturalOrd = Ord.contramap((kurssi: OppiaineenOsasuoritus) => { + const tunniste = kurssi.koulutusmoduuli.tunniste.koodiarvo + const match = tunniste.match(/([^\d]*)(\d*)/) + return match ? `${match[1]}${match[2].padStart(8, '0')}` : tunniste +})(string.Ord) + export const OppiaineenKurssit = ({ form, kurssit, @@ -317,26 +338,37 @@ export const OppiaineenKurssit = ({ onArviointi, onDeleteKurssi, onShowAddOsasuoritusDialog -}: OppiaineenKurssitProps) => ( -
- {kurssit.map((kurssi, index) => ( - a && onArviointi(index, a)} - onDelete={() => onDeleteKurssi(index)} - /> - ))} - {form.editMode && ( - - {t('Lisää osasuoritus')} - - )} -
-) +}: OppiaineenKurssitProps) => { + const sortedKurssit = useMemo( + () => A.sort(kurssiNaturalOrd)(kurssit), + [kurssit] + ) + + return ( +
+ + {sortedKurssit.map((kurssi, index) => ( + + a && onArviointi(index, a)} + onDelete={() => onDeleteKurssi(index)} + /> + + ))} + + {form.editMode && ( + + {t('Lisää osasuoritus')} + + )} +
+ ) +} type OppiaineArvosanaProps = { form: FormModel @@ -375,9 +407,9 @@ const OppiaineArvosana: React.FC = ({ format={koodiarvoOnly} /> ) : ( - + {arvioinnit ? parasArviointi(arvioinnit)?.arvosana.koodiarvo : '-'} - + ) } @@ -415,14 +447,16 @@ const PredictedGrade: React.FC = ({ } const oppiaineenNimi = (koulutusmoduuli: KoulutusmoduuliOf) => - [ - koulutusmoduuli.tunniste.nimi, - (koulutusmoduuli as any)?.kieli?.nimi, - (koulutusmoduuli as any)?.oppimäärä?.nimi - ] - .filter(notUndefined) - .map((s) => t(s)) - .join(', ') + pipe( + [ + (koulutusmoduuli as any)?.oppimäärä?.nimi || + koulutusmoduuli.tunniste.nimi, + (koulutusmoduuli as any)?.kieli?.nimi + ], + A.filter(notUndefined), + A.map(t), + (as) => as.join(', ') + ) type KurssiProps = { form: FormModel @@ -460,7 +494,10 @@ export const Kurssi: React.FC = ({ onBlur={closeTooltip} aria-describedby={tooltipId} > - {kurssi.koulutusmoduuli.tunniste.koodiarvo} + + {kurssi.koulutusmoduuli.tunniste.koodiarvo} + {isPaikallinenKoodi(kurssi.koulutusmoduuli.tunniste) && ' *'} + {form.editMode && ( = ({ suoritusClassName={kurssi.$class} format={koodiarvoOnly} /> - ) : kurssi.arviointi ? ( - parasArviointi(kurssi.arviointi as Arviointi[])?.arvosana.koodiarvo ) : ( - '-' + + {kurssi.arviointi + ? parasArviointi(kurssi.arviointi as Arviointi[])?.arvosana + .koodiarvo + : '–'} + )} {editModalVisible ? ( diff --git a/web/app/ib/IBEditor.tsx b/web/app/ib/IBEditor.tsx index 92206b9bb0..c049c21be6 100644 --- a/web/app/ib/IBEditor.tsx +++ b/web/app/ib/IBEditor.tsx @@ -2,6 +2,7 @@ import { isEmpty } from 'fp-ts/lib/Array' import React, { useCallback, useMemo } from 'react' import { useSchema } from '../appstate/constraints' import { useKoodistoFiller } from '../appstate/koodisto' +import { TestIdRoot, TestIdText } from '../appstate/useTestId' import { EditorContainer, usePäätasonSuoritus @@ -44,6 +45,7 @@ import { UusiPreIB2015OppiaineDialog } from './dialogs/UusiPreIB2015OppiaineDial import { UusiPreIB2015OsasuoritusDialog } from './dialogs/UusiPreIB2015OsasuoritusDialog' import { UusiPreIB2019OppiaineDialog } from './dialogs/UusiPreIB2019OppiaineDialog' import { UusiPreIB2019OsasuoritusDialog } from './dialogs/UusiPreIB2019OsasuoritusDialog' +import { containsPaikallinenSuoritus } from '../util/suoritus' export type IBEditorProps = AdaptedOpiskeluoikeusEditorProps @@ -90,6 +92,10 @@ const IBPäätasonSuoritusEditor: React.FC< const valmis = useOsasuorituksetValmiit(päätasonSuoritus.suoritus) + const paikallisiaSuorituksia = containsPaikallinenSuoritus( + päätasonSuoritus.suoritus + ) + return ( - - - - - - - - - UusiPreIB2015OsasuoritusDialog - ) - .isClass( - PreIBKoulutusmoduuli2019, - () => UusiPreIB2019OsasuoritusDialog - ) - .isClass(IBTutkinto, () => UusiIBTutkintoOsasuoritusDialog) - .get()} - /> - -
- {form.editMode && ( - - {t('Lisää oppiaine')} - - )} - {kurssejaYhteensä !== null && ( -
- {t('Suoritettujen kurssien määrä yhteensä')} - {': '} - {kurssejaYhteensä} -
- )} -
+ + + + + + + + + + UusiPreIB2015OsasuoritusDialog + ) + .isClass( + PreIBKoulutusmoduuli2019, + () => UusiPreIB2019OsasuoritusDialog + ) + .isClass(IBTutkinto, () => UusiIBTutkintoOsasuoritusDialog) + .get()} + /> + +
+ {form.editMode && ( + + {t('Lisää oppiaine')} + + )} + {kurssejaYhteensä !== null && ( + <> +
+ {t('Suoritettujen kurssien määrä yhteensä')} + {': '} + + {kurssejaYhteensä} + +
+ {paikallisiaSuorituksia && ( +
{`* = ${t('paikallinen kurssi tai oppiaine')}`}
+ )} + + )} +
+
{addOppiaineVisible && organisaatio && diff --git a/web/app/ib/IBPaatasonSuoritusTiedot.tsx b/web/app/ib/IBPaatasonSuoritusTiedot.tsx index 82e3a4552d..095572ae58 100644 --- a/web/app/ib/IBPaatasonSuoritusTiedot.tsx +++ b/web/app/ib/IBPaatasonSuoritusTiedot.tsx @@ -60,6 +60,7 @@ import { useKielivalikoimaOptions, useOppiaineTasoOptions } from './state/options' +import { TestIdText } from '../appstate/useTestId' export type IBTutkintTiedotProps = { form: FormModel @@ -76,7 +77,9 @@ export const IBPäätasonSuoritusTiedot: React.FC = ({ return ( - {t(ibKoulutusNimi(opiskeluoikeus))} + + {t(ibKoulutusNimi(opiskeluoikeus))} + = ({ view={KoodistoView} edit={KoodistoEdit} editProps={{ koodistoUri: 'kieli' }} + testId="suorituskieli" /> {hasPäätasonsuoritusOf(isIBTutkinnonSuoritus, päätasonSuoritus) && ( @@ -107,6 +111,7 @@ export const IBPäätasonSuoritusTiedot: React.FC = ({ path={path.prop('todistuksellaNäkyvätLisätiedot')} view={LocalizedTextView} edit={LocalizedTextEdit} + testId="todistuksellaNäkyvätLisätiedot" /> diff --git a/web/app/ib/dialogs/UusiPreIB2015OsasuoritusDialog.tsx b/web/app/ib/dialogs/UusiPreIB2015OsasuoritusDialog.tsx index a8fbca59d2..125d34b69d 100644 --- a/web/app/ib/dialogs/UusiPreIB2015OsasuoritusDialog.tsx +++ b/web/app/ib/dialogs/UusiPreIB2015OsasuoritusDialog.tsx @@ -48,7 +48,6 @@ import { export const UusiPreIB2015OsasuoritusDialog: AddOppiaineenOsasuoritusDialog< PreIBKurssinSuoritus2015 > = ({ onAdd, ...props }) => { - const koulutus = props.oppiaine.koulutusmoduuli const { preferences: paikallisetLukionKurssit, store: storePaikallinenLukionKurssi, @@ -63,7 +62,9 @@ export const UusiPreIB2015OsasuoritusDialog: AddOppiaineenOsasuoritusDialog< remove: removeIBKurssi } = usePreferences(props.organisaatioOid, 'ibkurssi') - const state = usePreIB2015OsasuoritusState() + const state = usePreIB2015OsasuoritusState( + props.oppiaine.koulutusmoduuli.tunniste + ) const valtakunnallisetTunnisteetOptions = useOppiaineenKurssiOptions( @@ -217,7 +218,7 @@ export const UusiPreIB2015OsasuoritusDialog: AddOppiaineenOsasuoritusDialog< koodiviiteId(state.lukiokurssinTyyppi.value) } onChange={(o) => state.lukiokurssinTyyppi.set(o?.value)} - testId="tunniste" + testId="tyyppi" /> )} @@ -245,8 +246,14 @@ export const UusiPreIB2015OsasuoritusDialog: AddOppiaineenOsasuoritusDialog< )} - {t('Peruuta')} - + + {t('Peruuta')} + + {t('Lisää')} diff --git a/web/app/ib/oppiaineet/preIBKurssi2015.ts b/web/app/ib/oppiaineet/preIBKurssi2015.ts index 351b3d3684..70917eef81 100644 --- a/web/app/ib/oppiaineet/preIBKurssi2015.ts +++ b/web/app/ib/oppiaineet/preIBKurssi2015.ts @@ -1,3 +1,4 @@ +import { t } from '../../i18n/i18n' import { IBKurssi } from '../../types/fi/oph/koski/schema/IBKurssi' import { Koodistokoodiviite } from '../../types/fi/oph/koski/schema/Koodistokoodiviite' import { LaajuusKursseissa } from '../../types/fi/oph/koski/schema/LaajuusKursseissa' @@ -9,9 +10,13 @@ import { import { PaikallinenLukionKurssi2015 } from '../../types/fi/oph/koski/schema/PaikallinenLukionKurssi2015' import { PreIBKurssi2015 } from '../../types/fi/oph/koski/schema/PreIBKurssi2015' import { PreIBKurssinSuoritus2015 } from '../../types/fi/oph/koski/schema/PreIBKurssinSuoritus2015' +import { PreIBSuorituksenOsasuoritus2015 } from '../../types/fi/oph/koski/schema/PreIBSuorituksenOsasuoritus2015' import { ValtakunnallinenLukionKurssi2015 } from '../../types/fi/oph/koski/schema/ValtakunnallinenLukionKurssi2015' +import { PermissiveKoodiviite } from '../../util/koodisto' +import { KoulutusmoduuliOf, TunnisteOf } from '../../util/schema' export type PreIBKurssi2015Props = { + oppiaineenTunniste: PreIB2015KurssiOppiaineenTunniste tunniste?: PreIB2015OsasuoritusTunniste lukiokurssinTyyppi?: Koodistokoodiviite<'lukionkurssintyyppi'> kuvaus?: LocalizedString @@ -19,6 +24,10 @@ export type PreIBKurssi2015Props = { laajuus?: LaajuusKursseissa } +export type PreIB2015KurssiOppiaineenTunniste = PermissiveKoodiviite< + TunnisteOf> +> + export type PreIB2015OsasuoritusTunniste = | Koodistokoodiviite | PaikallinenKoodi @@ -47,6 +56,7 @@ export const createPreIBKurssinSuoritus2015 = ( } const createPreIBKurssi2015 = ({ + oppiaineenTunniste, tunniste, lukiokurssinTyyppi, kuvaus, @@ -61,22 +71,29 @@ const createPreIBKurssi2015 = ({ }) } - if (lukiokurssinTyyppi && isPaikallinenKoodi(tunniste) && kuvaus) { - return PaikallinenLukionKurssi2015({ - tunniste, - kurssinTyyppi: lukiokurssinTyyppi, - kuvaus, - laajuus - }) - } - - if (isPaikallinenKoodi(tunniste) && kuvaus) { - return IBKurssi({ - tunniste, - kuvaus, - pakollinen: !!pakollinen, - laajuus - }) + if ( + isPaikallinenKoodi(tunniste) && + kuvaus && + t(tunniste.nimi) && + t(tunniste.koodiarvo) + ) { + if (oppiaineenTunniste.koodistoUri === 'oppiaineetib') { + return IBKurssi({ + tunniste, + kuvaus, + pakollinen: !!pakollinen, + laajuus + }) + } else { + return lukiokurssinTyyppi + ? PaikallinenLukionKurssi2015({ + tunniste, + kurssinTyyppi: lukiokurssinTyyppi, + kuvaus, + laajuus + }) + : null + } } return null diff --git a/web/app/ib/state/preIB2015Kurssi.ts b/web/app/ib/state/preIB2015Kurssi.ts index ac586e7f6b..58789a1619 100644 --- a/web/app/ib/state/preIB2015Kurssi.ts +++ b/web/app/ib/state/preIB2015Kurssi.ts @@ -9,6 +9,7 @@ import { LocalizedString } from '../../types/fi/oph/koski/schema/LocalizedString import { PreIBKurssinSuoritus2015 } from '../../types/fi/oph/koski/schema/PreIBKurssinSuoritus2015' import { createPreIBKurssinSuoritus2015, + PreIB2015KurssiOppiaineenTunniste, PreIB2015OsasuoritusTunniste, PreIBKurssi2015Props } from '../oppiaineet/preIBKurssi2015' @@ -30,7 +31,9 @@ export type PreIB2015OsasuoritusState = { export type UusiOsasuoritustyyppi = 'lukio' | 'ib' -export const usePreIB2015OsasuoritusState = (): PreIB2015OsasuoritusState => { +export const usePreIB2015OsasuoritusState = ( + oppiaineenTunniste: PreIB2015KurssiOppiaineenTunniste +): PreIB2015OsasuoritusState => { const tunniste = useDialogField(true) const uusiTyyppi = useDialogField(false) @@ -56,6 +59,7 @@ export const usePreIB2015OsasuoritusState = (): PreIB2015OsasuoritusState => { const result = useMemo( () => createPreIBKurssinSuoritus2015({ + oppiaineenTunniste, tunniste: tunniste.value, lukiokurssinTyyppi: lukiokurssinTyyppi.value, kuvaus: kuvaus.value, @@ -63,6 +67,7 @@ export const usePreIB2015OsasuoritusState = (): PreIB2015OsasuoritusState => { laajuus: laajuus.value }), [ + oppiaineenTunniste, tunniste.value, lukiokurssinTyyppi.value, kuvaus.value, diff --git a/web/app/util/suoritus.ts b/web/app/util/suoritus.ts index f4c030a870..0d75a2b75a 100644 --- a/web/app/util/suoritus.ts +++ b/web/app/util/suoritus.ts @@ -3,6 +3,8 @@ import { isArvioinniton } from '../types/fi/oph/koski/schema/Arvioinniton' import { Arviointi } from '../types/fi/oph/koski/schema/Arviointi' import { isMahdollisestiArvioinniton } from '../types/fi/oph/koski/schema/MahdollisestiArvioinniton' import { isPäätasonSuoritus } from '../types/fi/oph/koski/schema/PaatasonSuoritus' +import { isPaikallinenKoodi } from '../types/fi/oph/koski/schema/PaikallinenKoodi' +import { isPreIBKurssinSuoritus2015 } from '../types/fi/oph/koski/schema/PreIBKurssinSuoritus2015' import { Suoritus } from '../types/fi/oph/koski/schema/Suoritus' import { parasArviointi } from './arvioinnit' @@ -29,3 +31,11 @@ export const suoritusValmis = (suoritus: Suoritus) => { const isInPast = (dateStr?: string) => dateStr !== undefined && parseISODate(dateStr) <= new Date() + +export const containsPaikallinenSuoritus = (s: Suoritus): boolean => { + if (isPaikallinenKoodi(s.koulutusmoduuli.tunniste)) { + return true + } + const osasuoritukset: Suoritus[] | undefined = (s as any).osasuoritukset + return osasuoritukset?.some(containsPaikallinenSuoritus) ?? false +} diff --git a/web/test/e2e/base.ts b/web/test/e2e/base.ts index 7274c4f304..7921ec6755 100644 --- a/web/test/e2e/base.ts +++ b/web/test/e2e/base.ts @@ -13,6 +13,7 @@ import { KoskiEshOppijaPage } from './pages/oppija/KoskiEshOppijaPage' import { KoskiPerusopetusOppijaPage } from './pages/oppija/KoskiPerusopetusOppijaPage' import { KoskiTpoOppijaPage } from './pages/oppija/KoskiTpoOppijaPage' import { KoskiVSTOppijaPage } from './pages/oppija/KoskiVSTOppijaPage' +import { KoskiIBOppijaPage } from './pages/oppija/KoskiIBOppijaPage' type Fixtures = { virkailijaLoginPage: VirkailijaLoginPage @@ -27,6 +28,7 @@ type Fixtures = { kansalainenPage: KoskiKansalainenPage taiteenPerusopetusPage: KoskiTpoOppijaPage vstOppijaPage: KoskiVSTOppijaPage + ibOppijaPage: KoskiIBOppijaPage fixtures: KoskiFixtures makeAxeBuilder: () => AxeBuilder } @@ -68,6 +70,9 @@ export const test = base.extend({ vstOppijaPage: async ({ page }, use) => { await use(new KoskiVSTOppijaPage(page)) }, + ibOppijaPage: async ({ page }, use) => { + await use(new KoskiIBOppijaPage(page)) + }, fixtures: async ({ browser }, use) => { const ctx = await browser.newContext() const page = await ctx.newPage() diff --git a/web/test/e2e/ib.spec.ts b/web/test/e2e/ib.spec.ts new file mode 100644 index 0000000000..5efedf9485 --- /dev/null +++ b/web/test/e2e/ib.spec.ts @@ -0,0 +1,297 @@ +import { expect, test } from './base' +import { virkailija } from './setup/auth' + +const oppijaOid_IBFinalIina = '1.2.246.562.24.00000000060' + +test.describe('IB', () => { + test.use({ storageState: virkailija('kalle') }) + + test.beforeEach(async ({ oppijaPage }) => { + await oppijaPage.goto(oppijaOid_IBFinalIina) + }) + + test('Opiskeluoikeuden tiedot näytetään oikein', async ({ ibOppijaPage }) => { + const tiedot = ibOppijaPage.$.opiskeluoikeus + + await expect(tiedot.voimassaoloaika.elem).toHaveText( + 'Opiskeluoikeuden voimassaoloaika: 1.9.2012 – 4.6.2016' + ) + await expect(tiedot.tila.value.items(0).tila.elem).toHaveText('Valmistunut') + await expect(tiedot.tila.value.items(0).rahoitus.elem).toHaveText( + 'Valtionosuusrahoitteinen koulutus' + ) + await expect(tiedot.tila.value.items(1).tila.elem).toHaveText('Läsnä') + await expect(tiedot.tila.value.items(1).rahoitus.elem).toHaveText( + 'Valtionosuusrahoitteinen koulutus' + ) + }) + + test.describe('Pre-IB', () => { + test.describe('Kaikki tiedot näkyvissä', () => { + test('Suorituksen tiedot', async ({ ibOppijaPage }) => { + const suoritus = ibOppijaPage.$.suoritukset(0) + await expect(suoritus.koulutus.elem).toHaveText('Pre-IB') + await expect(await suoritus.organisaatio.value()).toEqual( + 'Ressun lukio' + ) + await expect(await suoritus.suorituskieli.value()).toEqual('englanti') + + const vahvistus = suoritus.suorituksenVahvistus.value + await expect(vahvistus.status.elem).toHaveText('Suoritus valmis') + await expect(vahvistus.henkilö(0).elem).toHaveText( + 'Reijo Reksi (rehtori)' + ) + }) + }) + + test('Oppiaineiden ja kurssien arvosanat näytetään', async ({ + ibOppijaPage + }) => { + await ibOppijaPage.testOppiaineryhmät({ + oppiaineet: [ + { + nimi: 'Äidinkieli ja kirjallisuus, Suomen kieli ja kirjallisuus', + arvosana: '8', + kurssit: [ + { tunniste: 'ÄI1', arvosana: '8', laajuus: 1 }, + { tunniste: 'ÄI2', arvosana: '8', laajuus: 1 }, + { tunniste: 'ÄI3', arvosana: '8', laajuus: 1 } + ] + }, + { + nimi: 'A1-kieli, englanti', + arvosana: '10', + kurssit: [ + { tunniste: 'ENA1', arvosana: '10', laajuus: 1 }, + { tunniste: 'ENA2', arvosana: '10', laajuus: 1 }, + { tunniste: 'ENA5', arvosana: '10', laajuus: 1 } + ] + }, + { + nimi: 'B1-kieli, ruotsi', + arvosana: '7', + kurssit: [ + { tunniste: 'RUB11', arvosana: '8', laajuus: 1 }, + { tunniste: 'RUB12', arvosana: '7', laajuus: 1 } + ] + }, + { + nimi: 'B2-kieli, ranska', + arvosana: '9', + kurssit: [ + { tunniste: 'RAN3', paikallinen: true, arvosana: '9', laajuus: 1 } + ] + }, + { + nimi: 'B3-kieli, espanja', + arvosana: '6', + kurssit: [ + { tunniste: 'ES1', paikallinen: true, arvosana: 'S', laajuus: 1 } + ] + }, + { + nimi: 'Matematiikka, pitkä oppimäärä', + arvosana: '7', + kurssit: [ + { tunniste: 'MAA2', arvosana: '7', laajuus: 1 }, + { tunniste: 'MAA11', arvosana: '7', laajuus: 1 }, + { tunniste: 'MAA12', arvosana: '7', laajuus: 1 }, + { tunniste: 'MAA13', arvosana: '7', laajuus: 1 } + ] + }, + { + nimi: 'Biologia', + arvosana: '8', + kurssit: [ + { tunniste: 'BI1', arvosana: '8', laajuus: 1 }, + { tunniste: 'BI10', paikallinen: true, arvosana: 'S', laajuus: 1 } + ] + }, + { + nimi: 'Maantieto', + arvosana: '10', + kurssit: [{ tunniste: 'GE2', arvosana: '10', laajuus: 1 }] + }, + { + nimi: 'Fysiikka', + arvosana: '7', + kurssit: [{ tunniste: 'FY1', arvosana: '7', laajuus: 1 }] + }, + { + nimi: 'Kemia', + arvosana: '8', + kurssit: [{ tunniste: 'KE1', arvosana: '8', laajuus: 1 }] + }, + { + nimi: 'Uskonto/Elämänkatsomustieto', + arvosana: '10', + kurssit: [{ tunniste: 'UK4', arvosana: '10', laajuus: 1 }] + }, + { + nimi: 'Filosofia', + arvosana: '7', + kurssit: [{ tunniste: 'FI1', arvosana: 'S', laajuus: 1 }] + }, + { + nimi: 'Psykologia', + arvosana: '8', + kurssit: [{ tunniste: 'PS1', arvosana: '8', laajuus: 1 }] + }, + { + nimi: 'Historia', + arvosana: '8', + kurssit: [ + { tunniste: 'HI3', arvosana: '9', laajuus: 1 }, + { tunniste: 'HI4', arvosana: '8', laajuus: 1 }, + { tunniste: 'HI10', paikallinen: true, arvosana: 'S', laajuus: 1 } + ] + }, + { + nimi: 'Yhteiskuntaoppi', + arvosana: '8', + kurssit: [{ tunniste: 'YH1', arvosana: '8', laajuus: 1 }] + }, + { + nimi: 'Liikunta', + arvosana: '8', + kurssit: [{ tunniste: 'LI1', arvosana: '8', laajuus: 1 }] + }, + { + nimi: 'Musiikki', + arvosana: '8', + kurssit: [{ tunniste: 'MU1', arvosana: '8', laajuus: 1 }] + }, + { + nimi: 'Kuvataide', + arvosana: '9', + kurssit: [{ tunniste: 'KU1', arvosana: '9', laajuus: 1 }] + }, + { + nimi: 'Terveystieto', + arvosana: '7', + kurssit: [{ tunniste: 'TE1', arvosana: '7', laajuus: 1 }] + }, + { + nimi: 'Opinto-ohjaus', + arvosana: '7', + kurssit: [{ tunniste: 'OP1', arvosana: 'S', laajuus: 1 }] + }, + { + nimi: 'Teemaopinnot', + arvosana: 'S', + kurssit: [ + { tunniste: 'MTA', paikallinen: true, arvosana: 'S', laajuus: 1 } + ] + } + ] + }) + + await expect( + ibOppijaPage.$.suoritukset(0).suoritettujaKurssejaYhteensä.elem + ).toHaveText('32') + }) + }) + + test.describe('Tietojen muokkaaminen', () => { + test.beforeEach(async ({ ibOppijaPage }) => { + await ibOppijaPage.edit() + }) + + test.describe('Suoritusten tiedot', () => { + test.describe('Oppiaine', () => { + test.describe('Lukion oppiaine', () => { + test('Arvosana-asteikko on oikea', async ({ ibOppijaPage }) => { + const oppiaine = ibOppijaPage.oppiaineryhmä().oppiaineet(0) + expect(await oppiaine.arvosana.options()).toEqual([ + '4', + '5', + '6', + '7', + '8', + '9', + '10', + 'H', + 'O', + 'S' + ]) + }) + + test('Arvosanan muuttaminen', async ({ ibOppijaPage }) => { + const oppiaine = ibOppijaPage.oppiaineryhmä().oppiaineet(0) + await oppiaine.arvosana.set('arviointiasteikkoyleissivistava_10') + await ibOppijaPage.tallenna() + await expect(oppiaine.arvosana.viewer).toHaveText('10') + }) + }) + + test.describe('Oppiaineen kurssi', () => { + test('Arvosanan muuttaminen', async ({ ibOppijaPage }) => { + const kurssi = ibOppijaPage.oppiaineryhmä().oppiaineet(0).kurssit(0) + await kurssi.arvosana.set('arviointiasteikkoyleissivistava_10') + await ibOppijaPage.tallenna() + await expect(kurssi.arvosana.viewer).toHaveText('10') + }) + + test('Valtakunnallisen kurssin voi lisätä ja poistaa', async ({ + ibOppijaPage + }) => { + const oppiaine = ibOppijaPage.oppiaineryhmä().oppiaineet(0) + await oppiaine.addKurssi.button.click() + + const modal = oppiaine.modal + await expect(modal.submit.button).toBeDisabled() + + await modal.tunniste.setByLabel('ÄI5 Teksti ja konteksti') + await expect(modal.submit.button).toBeDisabled() + + await modal.tyyppi.setByLabel('Pakollinen') + await modal.submit.button.click() + + const kurssi = oppiaine.kurssit(3) + await expect(kurssi.tunniste.elem).toHaveText('ÄI5') + + await kurssi.delete.button.click() + await expect(kurssi.tunniste.elem).not.toBeAttached() + }) + + test('Paikallisen kurssin voi lisätä ja poistaa', async ({ + ibOppijaPage + }) => { + const oppiaine = ibOppijaPage.oppiaineryhmä().oppiaineet(0) + await oppiaine.addKurssi.button.click() + + const modal = oppiaine.modal + await expect(modal.submit.button).toBeDisabled() + + await modal.tunniste.set( + 'Paikalliset lukion kurssit.__uusi_paikallinen_lukio__' + ) + await expect(modal.submit.button).toBeDisabled() + + await modal.paikallinenKoulutus.nimi.set( + 'Sosiaalisen median lukutaito' + ) + await expect(modal.submit.button).toBeDisabled() + + await modal.paikallinenKoulutus.koodiarvo.set('ÄI11') + await expect(modal.submit.button).toBeDisabled() + + await modal.paikallinenKoulutus.kuvaus.set( + 'Tutustutaan sosiaalisen median vaikutuskeinoihin' + ) + await expect(modal.submit.button).toBeDisabled() + + await modal.tyyppi.setByLabel('Pakollinen') + await modal.submit.button.click() + + const kurssi = oppiaine.kurssit(3) + await expect(kurssi.tunniste.elem).toHaveText('ÄI11 *') + + await kurssi.delete.button.click() + await expect(kurssi.tunniste.elem).not.toBeAttached() + }) + }) + }) + }) + }) +}) diff --git a/web/test/e2e/pages/oppija/KoskiIBOppijaPage.ts b/web/test/e2e/pages/oppija/KoskiIBOppijaPage.ts new file mode 100644 index 0000000000..f8472be654 --- /dev/null +++ b/web/test/e2e/pages/oppija/KoskiIBOppijaPage.ts @@ -0,0 +1,118 @@ +import { Page } from '@playwright/test' +import { sum } from '../../../../app/util/numbers' +import { expect } from '../../base' +import { KoskiOppijaPageV2 } from './KoskiOppijaPageV2' +import { arrayOf } from './uiV2builder/builder' +import { Button } from './uiV2builder/Button' +import { FormField } from './uiV2builder/controls' +import { Input } from './uiV2builder/Input' +import { Label } from './uiV2builder/Label' +import { OpiskeluoikeusHeader } from './uiV2builder/OpiskeluoikeusHeader' +import { Select } from './uiV2builder/Select' +import { SuorituksenVahvistus } from './uiV2builder/SuorituksenVahvistus' +import { Checkbox } from './uiV2builder/Checkbox' + +export class KoskiIBOppijaPage extends KoskiOppijaPageV2 { + constructor(page: Page) { + super(page, IBTestIds) + } + + async testOppiaineryhmät(...odotetutTiedot: OppiaineryhmätExpectedData[]) { + for (const aineryhmäIndex in odotetutTiedot) { + const { aineryhmä: aineryhmänNimi, oppiaineet } = + odotetutTiedot[aineryhmäIndex] + const oppiaineryhmä = this.oppiaineryhmä(0, parseInt(aineryhmäIndex)) + + if (aineryhmänNimi) { + await expect(oppiaineryhmä.nimi.elem).toHaveText(aineryhmänNimi) + } + + for (const oppiaineIndex in oppiaineet) { + const { nimi, arvosana, kurssit } = oppiaineet[oppiaineIndex] + const laajuus = sum(kurssit.map((kurssi) => kurssi.laajuus)) + const oppiaine = oppiaineryhmä.oppiaineet(parseInt(oppiaineIndex)) + + await expect(oppiaine.nimi.elem).toHaveText(nimi) + await expect(oppiaine.arvosana.viewer).toHaveText(arvosana) + await expect(oppiaine.laajuus.elem).toHaveText(laajuus.toString()) + + for (const kurssiIndex in kurssit) { + const { + tunniste, + arvosana: kurssinArvosana, + paikallinen + } = kurssit[kurssiIndex] + const kurssi = oppiaine.kurssit(parseInt(kurssiIndex)) + + await expect(kurssi.tunniste.elem).toHaveText( + paikallinen ? `${tunniste} *` : tunniste + ) + await expect(kurssi.arvosana.viewer).toHaveText(kurssinArvosana) + } + const olematonKurssi = oppiaine.kurssit(kurssit.length) + await expect(olematonKurssi.tunniste.elem).not.toBeAttached() + } + } + } + + oppiaineryhmä(suoritusIndex: number = 0, ryhmäIndex: number = 0) { + return this.$.suoritukset(suoritusIndex).oppiaineryhmät(ryhmäIndex) + } +} + +export const IBTestIds = { + opiskeluoikeus: OpiskeluoikeusHeader(), + suoritusTabs: arrayOf({ tab: Button }), + suoritukset: arrayOf({ + koulutus: Label, + organisaatio: FormField(Label, Select), + suorituskieli: FormField(Label, Select), + todistuksellaNäkyvätLisätiedot: FormField(Label, Input), + + suorituksenVahvistus: SuorituksenVahvistus(), + + oppiaineryhmät: arrayOf({ + nimi: Label, + oppiaineet: arrayOf({ + nimi: Label, + laajuus: Label, + arvosana: FormField(Label, Select), + kurssit: arrayOf({ + tunniste: Label, + arvosana: FormField(Label, Select), + delete: Button + }), + // Uuden kurssin lisäys: + addKurssi: Button, + modal: { + tunniste: Select, + tyyppi: Select, + laajuus: Input, + paikallinenKoulutus: { + nimi: Input, + koodiarvo: Input, + kuvaus: Input + }, + pakollinen: Checkbox, + cancel: Button, + submit: Button + } + }) + }), + suoritettujaKurssejaYhteensä: Label + }) +} + +export type OppiaineryhmätExpectedData = { + aineryhmä?: string + oppiaineet: Array<{ + nimi: string + arvosana: string + kurssit: Array<{ + tunniste: string + paikallinen?: boolean + arvosana: string + laajuus: number + }> + }> +} diff --git a/web/test/e2e/pages/oppija/uiV2builder/Label.ts b/web/test/e2e/pages/oppija/uiV2builder/Label.ts index ae67e6197d..d55bc742be 100644 --- a/web/test/e2e/pages/oppija/uiV2builder/Label.ts +++ b/web/test/e2e/pages/oppija/uiV2builder/Label.ts @@ -1,6 +1,7 @@ import { createControl } from './controls' export const Label = createControl((self) => ({ + elem: self, value: () => self.innerText(), waitFor: (timeout?: number) => self.waitFor({ timeout }), waitForToDisappear: (timeout?: number) => diff --git a/web/test/e2e/pages/oppija/uiV2builder/controls.ts b/web/test/e2e/pages/oppija/uiV2builder/controls.ts index 5905fc4623..d873082758 100644 --- a/web/test/e2e/pages/oppija/uiV2builder/controls.ts +++ b/web/test/e2e/pages/oppija/uiV2builder/controls.ts @@ -50,11 +50,13 @@ export const FormField = ( throw new Error('Trying to set an editor value without editor') } return edit.set(value) - } + }, + viewer: child('value') } }) as Control< E & { value: (editMode: boolean) => Promise set: (value: string) => Promise + viewer: Locator } >