Skip to content

Commit 9e122a6

Browse files
author
Greg Trihus
committed
TT-7017 Handle Flat projects
- Update PlanView to show flat projects - Update PassageCard to include FlatTitle - FlatTitle logic to render section titles based on cardInfo. - Created a comprehensive guide for Cypress component testing in the APM Vite application, detailing setup patterns, Material-UI testing strategies, common test categories, and debugging tips. - Added Cypress tests for the FlatTitle component, covering various scenarios including rendering based on section kind, handling empty titles, and special character support. - Integrated FlatTitle component into PassageCard and updated tests to verify its rendering and behavior. - Enhanced PlanView tests to ensure correct rendering of PassageCard for different kinds. - Cleaned up unused ESLint comments and improved code structure for better readability.
1 parent 96c148b commit 9e122a6

File tree

10 files changed

+1196
-15
lines changed

10 files changed

+1196
-15
lines changed

.github/instructions/cypress-testing.instructions.md

Lines changed: 402 additions & 0 deletions
Large diffs are not rendered by default.

src/renderer/src/components/Sheet/FlatTitle.cy.tsx

Lines changed: 550 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useMemo } from 'react';
2+
import { ISheet, SectionD, IwsKind } from '../../model';
3+
import { findRecord } from '../../crud';
4+
import { useGlobal } from '../../context/useGlobal';
5+
import { Box, Typography } from '@mui/material';
6+
7+
interface IFlatTitleProps {
8+
cardInfo: ISheet;
9+
}
10+
export const FlatTitle = (props: IFlatTitleProps) => {
11+
const { cardInfo } = props;
12+
const [memory] = useGlobal('memory');
13+
14+
const sectionTitle = useMemo(() => {
15+
if (cardInfo.kind === IwsKind.SectionPassage && cardInfo?.sectionId) {
16+
const section = findRecord(memory, 'section', cardInfo.sectionId?.id) as
17+
| SectionD
18+
| undefined;
19+
return section?.attributes.name || '';
20+
}
21+
return '';
22+
// eslint-disable-next-line react-hooks/exhaustive-deps
23+
}, [cardInfo]);
24+
25+
return cardInfo.kind === IwsKind.SectionPassage ? (
26+
<Box
27+
sx={{ display: 'flex', alignItems: 'center', width: '100%' }}
28+
data-cy="flat-title-box"
29+
>
30+
<Typography
31+
variant="body2"
32+
color="grey"
33+
sx={{ marginLeft: '0.5rem' }}
34+
data-cy="flat-title-typography"
35+
>
36+
<sup>{` \u21B3 `}</sup>
37+
{sectionTitle}
38+
</Typography>
39+
</Box>
40+
) : (
41+
<></>
42+
);
43+
};

src/renderer/src/components/Sheet/PassageCard.cy.tsx

Lines changed: 151 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import React from 'react';
22
import { PassageCard } from './PassageCard';
3-
import { ISheet, PassageTypeEnum, IwsKind, SheetLevel } from '../../model';
4-
import { RecordIdentity } from '@orbit/records';
3+
import {
4+
ISheet,
5+
PassageTypeEnum,
6+
IwsKind,
7+
SheetLevel,
8+
SectionD,
9+
} from '../../model';
10+
import { RecordIdentity, RecordRelationship } from '@orbit/records';
511
import { GlobalProvider } from '../../context/GlobalContext';
612
import { Provider } from 'react-redux';
713
import { legacy_createStore as createStore, combineReducers } from 'redux';
@@ -124,13 +130,57 @@ describe('PassageCard', () => {
124130
onPlayStatus?: () => void;
125131
isPlaying: boolean;
126132
isPersonal?: boolean;
127-
}
133+
},
134+
mockSections: SectionD[] = []
128135
) => {
129136
const initialState = createInitialState();
137+
138+
// Mock memory with sections for FlatTitle component
139+
const mockMemoryWithSections =
140+
mockSections.length > 0
141+
? ({
142+
...mockMemory,
143+
cache: {
144+
...mockMemory.cache,
145+
query: (queryBuilder: any) => {
146+
// Handle function-style queries (like findRecord uses)
147+
if (typeof queryBuilder === 'function') {
148+
const mockQueryBuilderInstance = {
149+
findRecord: (identity: { type: string; id: string }) => {
150+
return mockSections.find(
151+
(section) =>
152+
identity.type === 'section' &&
153+
section.id === identity.id
154+
);
155+
},
156+
findRecords: (type: string) => {
157+
if (type === 'section') return mockSections;
158+
return [];
159+
},
160+
};
161+
return queryBuilder(mockQueryBuilderInstance);
162+
}
163+
return mockSections;
164+
},
165+
},
166+
} as unknown as Memory)
167+
: mockMemory;
168+
169+
const finalInitialState =
170+
mockSections.length > 0
171+
? {
172+
...initialState,
173+
memory: mockMemoryWithSections,
174+
coordinator: {
175+
getSource: () => mockMemoryWithSections,
176+
} as unknown as Coordinator,
177+
}
178+
: initialState;
179+
130180
cy.mount(
131181
<Provider store={mockStore}>
132-
<GlobalProvider init={initialState}>
133-
<DataProvider dataStore={mockMemory}>
182+
<GlobalProvider init={finalInitialState}>
183+
<DataProvider dataStore={mockMemoryWithSections}>
134184
<PassageCard
135185
cardInfo={cardInfo}
136186
getBookName={props.getBookName}
@@ -190,6 +240,39 @@ describe('PassageCard', () => {
190240
type,
191241
});
192242

243+
const createMockSection = (overrides: Partial<SectionD> = {}): SectionD => {
244+
return {
245+
id: 'section-1',
246+
type: 'section',
247+
attributes: {
248+
sequencenum: 1,
249+
name: 'Test Section Title',
250+
graphics: '',
251+
published: false,
252+
publishTo: '',
253+
level: 1,
254+
state: '',
255+
dateCreated: '2024-01-01T00:00:00Z',
256+
dateUpdated: '2024-01-01T00:00:00Z',
257+
lastModifiedBy: 1,
258+
},
259+
keys: {
260+
remoteId: '1',
261+
},
262+
relationships: {
263+
plan: {} as RecordRelationship,
264+
passages: {} as RecordRelationship,
265+
transcriber: {} as RecordRelationship,
266+
editor: {} as RecordRelationship,
267+
group: {} as RecordRelationship,
268+
titleMediafile: {} as RecordRelationship,
269+
lastModifiedByUser: {} as RecordRelationship,
270+
organizationScheme: {} as RecordRelationship,
271+
},
272+
...overrides,
273+
};
274+
};
275+
193276
it('should render card with basic information', () => {
194277
const cardInfo = createMockSheet();
195278
mountPassageCard(cardInfo, {
@@ -1380,4 +1463,67 @@ describe('PassageCard', () => {
13801463
cy.get('div[class*="MuiAvatar-root"]').should('not.exist');
13811464
});
13821465
});
1466+
1467+
describe('FlatTitle Integration', () => {
1468+
it('should render FlatTitle component for SectionPassage kind', () => {
1469+
const mockSection = createMockSection({
1470+
id: 'section-1',
1471+
attributes: {
1472+
sequencenum: 1,
1473+
name: 'Test Section for FlatTitle',
1474+
graphics: '',
1475+
published: false,
1476+
publishTo: '',
1477+
level: 1,
1478+
state: '',
1479+
dateCreated: '2024-01-01T00:00:00Z',
1480+
dateUpdated: '2024-01-01T00:00:00Z',
1481+
lastModifiedBy: 1,
1482+
},
1483+
});
1484+
1485+
const cardInfo = createMockSheet({
1486+
kind: IwsKind.SectionPassage,
1487+
sectionId: {
1488+
id: 'section-1',
1489+
type: 'section',
1490+
},
1491+
});
1492+
1493+
mountPassageCard(
1494+
cardInfo,
1495+
{
1496+
getBookName: mockGetBookName,
1497+
handleViewStep: mockHandleViewStep,
1498+
isPlaying: false,
1499+
},
1500+
[mockSection]
1501+
);
1502+
1503+
// Should render FlatTitle component with section title
1504+
cy.get('[data-cy="flat-title-box"]').should('be.visible');
1505+
cy.get('[data-cy="flat-title-typography"]')
1506+
.should('be.visible')
1507+
.and('contain.text', 'Test Section for FlatTitle');
1508+
1509+
// Should contain the arrow symbol
1510+
cy.get('sup').should('contain.text', ' ↳ ');
1511+
});
1512+
1513+
it('should not render FlatTitle component for non-SectionPassage kind', () => {
1514+
const cardInfo = createMockSheet({
1515+
kind: IwsKind.Passage, // Regular passage, not section passage
1516+
});
1517+
1518+
mountPassageCard(cardInfo, {
1519+
getBookName: mockGetBookName,
1520+
handleViewStep: mockHandleViewStep,
1521+
isPlaying: false,
1522+
});
1523+
1524+
// Should not render FlatTitle component
1525+
cy.get('[data-cy="flat-title-box"]').should('not.exist');
1526+
cy.get('[data-cy="flat-title-typography"]').should('not.exist');
1527+
});
1528+
});
13831529
});

src/renderer/src/components/Sheet/PassageCard.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { PlayButton } from '../PlayButton';
1717
import { cardsSelector } from '../../selector';
1818
import { shallowEqual, useSelector } from 'react-redux';
1919
import { stringAvatar } from '../../utils';
20+
import { FlatTitle } from './FlatTitle';
2021

2122
interface IProps {
2223
cardInfo: ISheet;
@@ -39,11 +40,8 @@ export function PassageCard(props: IProps) {
3940
isPersonal,
4041
} = props;
4142
const t: ICardsStrings = useSelector(cardsSelector, shallowEqual);
42-
4343
const fullBookName = getBookName(cardInfo.book);
44-
4544
const noteTitle = cardInfo?.sharedResource?.attributes.title;
46-
4745
const ref = noteTitle || cardInfo.passage?.attributes.reference;
4846

4947
const comment =
@@ -127,6 +125,7 @@ export function PassageCard(props: IProps) {
127125
<></>
128126
)}
129127
</Box>
128+
<FlatTitle cardInfo={cardInfo} />
130129
{psgType !== PassageTypeEnum.CHAPTERNUMBER ? (
131130
<>
132131
<Typography variant="body2" color="grey">

src/renderer/src/components/Sheet/PlanSheet.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,6 @@ export function PlanSheet(props: IProps) {
703703
onFilterChange({ ...filterState, disabled: true }, false);
704704
};
705705

706-
// eslint-disable-next-line react-hooks/exhaustive-deps
707706
const filtered = useMemo(() => {
708707
return (
709708
!filterState.disabled &&

src/renderer/src/components/Sheet/PlanView.cy.tsx

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ describe('PlanView', () => {
625625
cy.get('div[class*="MuiCard-root"]').should('not.exist');
626626
});
627627

628-
it('should return null for rows with kind other than 0 or 1', () => {
628+
it('should return null for rows with kind other than IwsKind.Section, IwsKind.Passage, or IwsKind.SectionPassage', () => {
629629
const invalidRow = {
630630
...createMockSection(),
631631
kind: 99 as any, // Invalid kind
@@ -676,4 +676,44 @@ describe('PlanView', () => {
676676
.eq(1)
677677
.should('have.css', 'margin-left');
678678
});
679+
680+
it('should render PassageCard for IwsKind.Passage', () => {
681+
const passageRow = createMockPassage({
682+
kind: IwsKind.Passage,
683+
});
684+
const rowInfo: ISheet[] = [passageRow];
685+
const bookMap = createMockBookNameMap();
686+
687+
mountPlanView({
688+
rowInfo,
689+
bookMap,
690+
publishingView: false,
691+
handleOpenPublishDialog: mockHandleOpenPublishDialog,
692+
handleGraphic: mockHandleGraphic,
693+
});
694+
695+
cy.wait(100);
696+
// Should render PassageCard for IwsKind.Passage
697+
cy.get('div[class*="MuiCard-root"]').should('be.visible');
698+
});
699+
700+
it('should render PassageCard for IwsKind.SectionPassage', () => {
701+
const sectionPassageRow = createMockPassage({
702+
kind: IwsKind.SectionPassage,
703+
});
704+
const rowInfo: ISheet[] = [sectionPassageRow];
705+
const bookMap = createMockBookNameMap();
706+
707+
mountPlanView({
708+
rowInfo,
709+
bookMap,
710+
publishingView: false,
711+
handleOpenPublishDialog: mockHandleOpenPublishDialog,
712+
handleGraphic: mockHandleGraphic,
713+
});
714+
715+
cy.wait(100);
716+
// Should render PassageCard for IwsKind.SectionPassage
717+
cy.get('div[class*="MuiCard-root"]').should('be.visible');
718+
});
679719
});

src/renderer/src/components/Sheet/PlanView.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
PassageTypeEnum,
88
IPlanSheetStrings,
99
OrganizationD,
10+
IwsKind,
1011
} from '../../model';
1112
import { Button, Box, Typography, Grid } from '@mui/material';
1213
import PublishOnIcon from '@mui/icons-material/PublicOutlined';
@@ -164,7 +165,10 @@ export function PlanView(props: IProps) {
164165
) : null}
165166
</Box>
166167
);
167-
} else if (row.kind === 1) {
168+
} else if (
169+
row.kind === IwsKind.Passage ||
170+
row.kind === IwsKind.SectionPassage
171+
) {
168172
const mediaId = row.mediaId?.id;
169173
return (
170174
<PassageCard

src/renderer/src/components/Sheet/ScriptureTable.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,10 +343,10 @@ export function ScriptureTable(props: IProps) {
343343
filterState.maxStep !== '' ||
344344
filterState.hideDone ||
345345
filterState.minSection > minSection ||
346-
(filterState.maxSection > -1 && filterState.maxSection < maximumSection) ||
346+
(filterState.maxSection > -1 &&
347+
filterState.maxSection < maximumSection) ||
347348
filterState.assignedToMe)
348349
);
349-
// eslint-disable-next-line react-hooks/exhaustive-deps
350350
}, [filterState, minSection, maximumSection]);
351351

352352
const publishingTitle = (passageType: PassageTypeEnum) =>
@@ -1277,7 +1277,6 @@ export function ScriptureTable(props: IProps) {
12771277
return () => {
12781278
window.removeEventListener('resize', handleResize);
12791279
};
1280-
// eslint-disable-next-line react-hooks/exhaustive-deps
12811280
}, []); //do this once to get the default;
12821281

12831282
useEffect(() => {

src/renderer/src/components/Sheet/usePlanSheetFill.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,6 @@ export const usePlanSheetFill = ({
219219
const ActivateCell: ICellEditor = (props: any) => {
220220
doSetActive();
221221
props.onRevert();
222-
// eslint-disable-next-line react-hooks/exhaustive-deps
223222
return <></>;
224223
};
225224

0 commit comments

Comments
 (0)