Skip to content

Commit

Permalink
fix: lunatic navigation in loops (#796)
Browse files Browse the repository at this point in the history
- fix: insert subsequence components in loops
- refactor: LunaticPaginationMode in LunaticParameters class 
- test: add unit and functional tests on the Lunatic pagination feature
- docs: comment about max page for non paginated loops
- style: fix typos here and there
  • Loading branch information
nsenave committed Nov 25, 2023
1 parent ebce519 commit 5d5c439
Show file tree
Hide file tree
Showing 20 changed files with 362,669 additions and 26 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {

allprojects {
group = 'fr.insee.eno'
version = '3.12.3-SNAPSHOT'
version = '3.12.4-SNAPSHOT'
sourceCompatibility = '17'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public enum Context {HOUSEHOLD, BUSINESS, DEFAULT}
public enum ModeParameter {CAPI, CATI, CAWI, PAPI, PROCESS}
public enum Language {FR, EN, IT, ES, DE}
public enum QuestionNumberingMode {NONE, SEQUENCE, ALL}
public enum LunaticPaginationMode {NONE, SEQUENCE, QUESTION}

public static final String DEFAULT_CAMPAIGN_NAME = "test-2020-x00";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
@Setter
public class LunaticParameters {

public enum LunaticPaginationMode {NONE, SEQUENCE, QUESTION}

private boolean controls;
private boolean toolTip; // Not implemented yet in Lunatic
private boolean missingVariables;
private boolean filterResult;
private boolean filterDescription;
private EnoParameters.LunaticPaginationMode lunaticPaginationMode;
private LunaticPaginationMode lunaticPaginationMode;

private LunaticParameters() {}

Expand Down Expand Up @@ -41,8 +43,8 @@ private void lunaticValues(EnoParameters.Context context, EnoParameters.ModePara
this.setMissingVariables(isInterview);
this.setLunaticPaginationMode(
EnoParameters.Context.BUSINESS.equals(context) ?
EnoParameters.LunaticPaginationMode.SEQUENCE :
EnoParameters.LunaticPaginationMode.QUESTION);
LunaticPaginationMode.SEQUENCE :
LunaticPaginationMode.QUESTION);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,11 @@ private int insertSequenceInLoop(
private void insertComponentsInLoop(
Questionnaire lunaticQuestionnaire, Loop lunaticLoop, String sequenceReference) {
AbstractSequence enoSequence = (AbstractSequence) enoIndex.get(sequenceReference);
enoSequence.getSequenceStructure().forEach(structureItemReference ->
relocateComponent(lunaticQuestionnaire, lunaticLoop, structureItemReference.getId()));
enoSequence.getSequenceStructure().forEach(structureItemReference -> {
relocateComponent(lunaticQuestionnaire, lunaticLoop, structureItemReference.getId());
if (StructureItemType.SUBSEQUENCE.equals(structureItemReference.getType()))
insertComponentsInLoop(lunaticQuestionnaire, lunaticLoop, structureItemReference.getId());
});
}

/** Relocate the component with given reference (id) from the questionnaire's components
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package fr.insee.eno.core.processing.out.steps.lunatic.pagination;

import fr.insee.eno.core.parameter.EnoParameters;
import fr.insee.eno.core.parameter.LunaticParameters;
import fr.insee.eno.core.processing.ProcessingStep;
import fr.insee.lunatic.model.flat.Questionnaire;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LunaticAddPageNumbers implements ProcessingStep<Questionnaire> {

private final EnoParameters.LunaticPaginationMode mode;
private final LunaticParameters.LunaticPaginationMode mode;

public LunaticAddPageNumbers(EnoParameters.LunaticPaginationMode mode) {
public LunaticAddPageNumbers(LunaticParameters.LunaticPaginationMode mode) {
this.mode = mode;
}

Expand All @@ -37,7 +37,7 @@ public void apply(Questionnaire lunaticQuestionnaire) {
}

// TODO: enum in Lunatic-Model for this...
public static String lunaticNumberingMode(EnoParameters.LunaticPaginationMode paginationMode) {
public static String lunaticNumberingMode(LunaticParameters.LunaticPaginationMode paginationMode) {
return switch (paginationMode) {
case NONE -> "none";
case SEQUENCE -> "sequence";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public void applyNumPageOnLoop(Loop loop, String numPagePrefix, int pageCount) {
.count();

loop.setMaxPage(Long.toString(maxPage));
}
} // Note: shouldn't be max page equal to "1" if non paginated loop?
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ private static String rawSerialization(Questionnaire lunaticQuestionnaire) {
* @return json string of the questionnaire with resize included
* @throws LunaticSerializationException serialization exception
*/
public static String extendedSerialization(String lunaticJson, Questionnaire lunaticQuestionnaire)
private static String extendedSerialization(String lunaticJson, Questionnaire lunaticQuestionnaire)
throws LunaticSerializationException {
ObjectMapper mapper = new ObjectMapper();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ void lunaticParameters_DefaultCAWILunatic_testLunaticValues() {
assertFalse(lunaticParameters.isFilterDescription());
assertTrue(lunaticParameters.isFilterResult());
assertFalse(lunaticParameters.isMissingVariables());
assertEquals(EnoParameters.LunaticPaginationMode.QUESTION, lunaticParameters.getLunaticPaginationMode());
assertEquals(LunaticParameters.LunaticPaginationMode.QUESTION, lunaticParameters.getLunaticPaginationMode());
}

@Test
Expand Down Expand Up @@ -65,7 +65,7 @@ void parameters_BusinessCAPILunatic_lunaticPagination() {
EnoParameters.Context.BUSINESS, EnoParameters.ModeParameter.CAPI, Format.LUNATIC);
//
LunaticParameters lunaticParameters = enoParameters.getLunaticParameters();
assertEquals(EnoParameters.LunaticPaginationMode.SEQUENCE, lunaticParameters.getLunaticPaginationMode());
assertEquals(LunaticParameters.LunaticPaginationMode.SEQUENCE, lunaticParameters.getLunaticPaginationMode());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import fr.insee.eno.core.mappers.LunaticMapper;
import fr.insee.eno.core.model.EnoQuestionnaire;
import fr.insee.eno.core.parameter.EnoParameters;
import fr.insee.eno.core.parameter.LunaticParameters;
import fr.insee.eno.core.processing.common.steps.EnoAddPrefixInQuestionLabels;
import fr.insee.eno.core.processing.common.steps.EnoAddResponseTimeSection;
import fr.insee.eno.core.processing.out.steps.lunatic.pagination.LunaticAddPageNumbers;
Expand Down Expand Up @@ -31,7 +32,7 @@ void questionPaginationMode_hoursAndMinutesQuestionShouldHaveTheSameNumber() {
LunaticMapper lunaticMapper = new LunaticMapper();
lunaticMapper.mapQuestionnaire(enoQuestionnaire, lunaticQuestionnaire);
//
new LunaticAddPageNumbers(EnoParameters.LunaticPaginationMode.QUESTION).apply(lunaticQuestionnaire);
new LunaticAddPageNumbers(LunaticParameters.LunaticPaginationMode.QUESTION).apply(lunaticQuestionnaire);

// When
new LunaticResponseTimeQuestionPagination().apply(lunaticQuestionnaire);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,7 @@ void init() {

questionnaire.getComponents().addAll(components);

//JSONSerializer jsonSerializer = new JSONSerializer();
//System.out.println(jsonSerializer.serialize2(questionnaire));
processing.apply(questionnaire);
//System.out.println(jsonSerializer.serialize2(questionnaire));
}

@Test
Expand Down Expand Up @@ -139,7 +136,7 @@ void shouldComponentsInPairwiseLinksToHaveSamePage() {
}

@Test
void shouldComponentsInPaginatedLoopToHaveDifferentsPage() {
void shouldComponentsInPaginatedLoopToHaveDifferentPage() {
// l7 is paginated loop so we'll increment subcomponents
assertTrue(l7.getPaginatedLoop());
assertEquals("7", l7.getPage());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package fr.insee.eno.core.processing.out.steps.lunatic.pagination;

import fr.insee.eno.core.DDIToEno;
import fr.insee.eno.core.exceptions.business.DDIParsingException;
import fr.insee.eno.core.mappers.LunaticMapper;
import fr.insee.eno.core.model.EnoQuestionnaire;
import fr.insee.eno.core.parameter.EnoParameters;
import fr.insee.eno.core.parameter.Format;
import fr.insee.eno.core.processing.out.steps.lunatic.LunaticAddHierarchy;
import fr.insee.eno.core.processing.out.steps.lunatic.LunaticLoopResolution;
import fr.insee.eno.core.processing.out.steps.lunatic.LunaticSortComponents;
import fr.insee.eno.core.processing.out.steps.lunatic.table.LunaticTableProcessing;
import fr.insee.lunatic.model.flat.ComponentTypeEnum;
import fr.insee.lunatic.model.flat.Loop;
import fr.insee.lunatic.model.flat.Questionnaire;
import fr.insee.lunatic.model.flat.Subsequence;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class LunaticPaginationFunctionalTests {

private Questionnaire mapDDIAndApplyPagination(String id) throws DDIParsingException {
// Given
EnoParameters enoParameters = EnoParameters.of(EnoParameters.Context.HOUSEHOLD, EnoParameters.ModeParameter.CAWI, Format.LUNATIC);
EnoQuestionnaire enoQuestionnaire = DDIToEno.transform(
LunaticPaginationFunctionalTests.class.getClassLoader().getResourceAsStream(
"functional/ddi/pagination/ddi-"+id+".xml"),
enoParameters);
Questionnaire lunaticQuestionnaire = new Questionnaire();
LunaticMapper lunaticMapper = new LunaticMapper();
lunaticMapper.mapEnoObject(enoQuestionnaire, lunaticQuestionnaire);
new LunaticSortComponents(enoQuestionnaire).apply(lunaticQuestionnaire);
new LunaticLoopResolution(enoQuestionnaire).apply(lunaticQuestionnaire);
new LunaticTableProcessing(enoQuestionnaire).apply(lunaticQuestionnaire);
new LunaticAddHierarchy().apply(lunaticQuestionnaire);

// When
new LunaticAddPageNumbers(enoParameters.getLunaticParameters().getLunaticPaginationMode())
.apply(lunaticQuestionnaire);

return lunaticQuestionnaire;
}

@Test
void functionalTest1() throws DDIParsingException {
Questionnaire lunaticQuestionnaire = mapDDIAndApplyPagination("llxh9g6g");

// Then
assertNotNull(lunaticQuestionnaire.getMaxPage());
// ...
}

@Test
void functionalTest2() throws DDIParsingException {
Questionnaire lunaticQuestionnaire = mapDDIAndApplyPagination("lnycjn6n");

// Then
assertEquals("3", lunaticQuestionnaire.getMaxPage());

//
assertEquals(ComponentTypeEnum.LOOP, lunaticQuestionnaire.getComponents().get(0).getComponentType());
Loop loop1 = (Loop) lunaticQuestionnaire.getComponents().get(0);
assertEquals("1", loop1.getPage());
assertNull(loop1.getMaxPage());
assertEquals(2, loop1.getComponents().size());
loop1.getComponents().forEach(component ->
assertEquals("1", component.getPage()));

//
assertEquals(ComponentTypeEnum.LOOP, lunaticQuestionnaire.getComponents().get(1).getComponentType());
Loop loop2 = (Loop) lunaticQuestionnaire.getComponents().get(1);
assertEquals("2", loop2.getPage());
assertEquals("492", loop2.getMaxPage());
assertEquals("2.1", loop2.getComponents().get(0).getPage());
assertEquals("2.2", loop2.getComponents().get(1).getPage());
assertEquals("2.3", loop2.getComponents().get(2).getPage());
// ...
assertEquals("2.7", loop2.getComponents().get(6).getPage());
assertEquals(ComponentTypeEnum.SUBSEQUENCE, loop2.getComponents().get(7).getComponentType());
assertEquals("2.8", loop2.getComponents().get(7).getPage());
assertEquals("2.8", ((Subsequence) loop2.getComponents().get(7)).getGoToPage());
assertEquals("2.9", loop2.getComponents().get(8).getPage());
// ...

//
assertEquals(ComponentTypeEnum.SEQUENCE, lunaticQuestionnaire.getComponents().get(2).getComponentType());
assertEquals("3", lunaticQuestionnaire.getComponents().get(2).getPage());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package fr.insee.eno.core.processing.out.steps.lunatic.pagination;

import fr.insee.eno.core.parameter.LunaticParameters;
import fr.insee.lunatic.model.flat.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

/**
* Other tests on the Lunatic pagination.
*/
class LunaticPaginationTest {

@Nested
class LoopOnSequence {

private Questionnaire questionnaire;

@BeforeEach
void createLoopWithSequence() {
questionnaire = new Questionnaire();
Sequence sequence = new Sequence();
sequence.setComponentType(ComponentTypeEnum.SEQUENCE);
Input input1 = new Input();
input1.setComponentType(ComponentTypeEnum.INPUT);
Input input2 = new Input();
input2.setComponentType(ComponentTypeEnum.INPUT);
Loop loop = new Loop();
loop.setComponentType(ComponentTypeEnum.LOOP);
loop.setLines(new LinesLoop());
loop.getComponents().add(sequence);
loop.getComponents().add(input1);
loop.getComponents().add(input2);
questionnaire.getComponents().add(loop);
}

@Test
void questionMode() {
//
new LunaticAddPageNumbers(LunaticParameters.LunaticPaginationMode.QUESTION).apply(questionnaire);
//
assertEquals("question", questionnaire.getPagination());
assertEquals("1", questionnaire.getMaxPage());
Loop loop = (Loop) questionnaire.getComponents().get(0);
assertFalse(loop.getPaginatedLoop());
// Non paginated loops don't have a "max page" property
assertNull(loop.getMaxPage());
// Non paginated loops don't use "dotted" numbering
loop.getComponents().forEach(loopComponent ->
assertEquals("1", loopComponent.getPage()));
}

@Test
void sequenceMode() {
//
new LunaticAddPageNumbers(LunaticParameters.LunaticPaginationMode.SEQUENCE).apply(questionnaire);
//
assertEquals("sequence", questionnaire.getPagination());
assertEquals("1", questionnaire.getMaxPage());
Loop loop = (Loop) questionnaire.getComponents().get(0);
assertTrue(loop.getPaginatedLoop());
// Paginated loops have a "max page"
assertEquals("1", loop.getMaxPage());
// Sequence mode: each component of the sequence is on the same page
loop.getComponents().forEach(loopComponent ->
assertEquals("1.1", loopComponent.getPage()));
}
}

@Nested
class LoopOnTwoSequences {

private Questionnaire questionnaire;

@BeforeEach
void createLoopWithTwoSequences() {
questionnaire = new Questionnaire();
Sequence sequence1 = new Sequence();
sequence1.setComponentType(ComponentTypeEnum.SEQUENCE);
Input input1 = new Input();
input1.setComponentType(ComponentTypeEnum.INPUT);
Sequence sequence2 = new Sequence();
sequence2.setComponentType(ComponentTypeEnum.SEQUENCE);
Input input2 = new Input();
input2.setComponentType(ComponentTypeEnum.INPUT);
Loop loop = new Loop();
loop.setComponentType(ComponentTypeEnum.LOOP);
loop.setLines(new LinesLoop());
loop.getComponents().add(sequence1);
loop.getComponents().add(input1);
loop.getComponents().add(sequence2);
loop.getComponents().add(input2);
questionnaire.getComponents().add(loop);
}

@Test
void questionMode() {
//
new LunaticAddPageNumbers(LunaticParameters.LunaticPaginationMode.QUESTION).apply(questionnaire);
//
assertEquals("question", questionnaire.getPagination());
assertEquals("1", questionnaire.getMaxPage());
Loop loop = (Loop) questionnaire.getComponents().get(0);
assertFalse(loop.getPaginatedLoop());
// Non paginated loops don't have a "max page" property
assertNull(loop.getMaxPage());
// Non paginated loops don't use "dotted" numbering
loop.getComponents().forEach(loopComponent ->
assertEquals("1", loopComponent.getPage()));
}

@Test
void sequenceMode() {
//
new LunaticAddPageNumbers(LunaticParameters.LunaticPaginationMode.SEQUENCE).apply(questionnaire);
//
assertEquals("sequence", questionnaire.getPagination());
assertEquals("1", questionnaire.getMaxPage());
Loop loop = (Loop) questionnaire.getComponents().get(0);
assertTrue(loop.getPaginatedLoop());
// Paginated loops have a "max page"
assertEquals("2", loop.getMaxPage());
// Sequence mode: each component of the sequence is on the same page
assertEquals("1.1", loop.getComponents().get(0).getPage());
assertEquals("1.1", loop.getComponents().get(1).getPage());
assertEquals("1.2", loop.getComponents().get(2).getPage());
assertEquals("1.2", loop.getComponents().get(3).getPage());
}
}

@Nested
class LoopOnSubsequence {
private Questionnaire questionnaire;
@BeforeEach
void createLoopWithSubsequence() {
questionnaire = new Questionnaire();
}
@Test
void questionMode() {
assertNotNull(questionnaire);
}
}

}
Loading

0 comments on commit 5d5c439

Please sign in to comment.