Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface MappingEntryCardsProps {
groupId: string;
mappingCards: RenderedMappingCard[];
cardOffsetsByGroup: Record<string, Record<string, number>>;
cardHeightsByGroup: Record<string, Record<string, number>>;
hoveredMappingKeys: string[];
onSetHoveredMappingKeys: (keys: string[]) => void;
setCardWrapperRef: (cardKey: string) => RefCallback<HTMLDivElement>;
Expand All @@ -22,13 +23,21 @@ export const MappingEntryCards = ({
groupId,
mappingCards,
cardOffsetsByGroup,
cardHeightsByGroup,
hoveredMappingKeys,
onSetHoveredMappingKeys,
setCardWrapperRef,
}: MappingEntryCardsProps): JSX.Element => {
const offsets = cardOffsetsByGroup[groupId] ?? {};
const heights = cardHeightsByGroup[groupId] ?? {};
const minHeight = Math.max(
0,
...mappingCards.map((card) => (offsets[card.key] ?? 0) + (heights[card.key] ?? 28))
);

return (
<Box data-testid={`mapping-rail-${groupId}`} style={railStyles}>
<Box style={{ position: 'relative', minHeight: '100%' }}>
<Box style={{ position: 'relative', minHeight: Math.max(minHeight, 0) }}>
{mappingCards.length > 0
? mappingCards.map((mappingCard) => (
<MappingCard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ export const MappingView = ({
const [cardOffsetsByGroup, setCardOffsetsByGroup] = useState<
Record<string, Record<string, number>>
>({});
const [cardHeightsByGroup, setCardHeightsByGroup] = useState<
Record<string, Record<string, number>>
>({});
const [editModalState, setEditModalState] = useState<EditModalState>(EMPTY_EDIT_MODAL);
const [pendingTextExclusionRanges, setPendingTextExclusionRanges] = useState<
TextExclusionRange[] | null
Expand Down Expand Up @@ -492,6 +495,7 @@ export const MappingView = ({
useLayoutEffect(() => {
const measureOffsets = () => {
const nextOffsets: Record<string, Record<string, number>> = {};
const nextHeights: Record<string, Record<string, number>> = {};

allGroups.forEach((group) => {
const groupNode = groupLayoutRefs.current[group.id];
Expand All @@ -516,12 +520,21 @@ export const MappingView = ({
});

nextOffsets[group.id] = resolveMarkerOffsets(cards);
nextHeights[group.id] = Object.fromEntries(cards.map((c) => [c.key, c.height]));
});

setCardOffsetsByGroup(nextOffsets);
setCardHeightsByGroup(nextHeights);
};

measureOffsets();

const observer = new ResizeObserver(measureOffsets);
Object.values(cardWrapperRefs.current).forEach((node) => {
if (node) observer.observe(node);
});

return () => observer.disconnect();
}, [allGroups]);

const openAssignModal = (
Expand Down Expand Up @@ -1069,6 +1082,7 @@ export const MappingView = ({
groupId={group.id}
mappingCards={group.mappingCards}
cardOffsetsByGroup={cardOffsetsByGroup}
cardHeightsByGroup={cardHeightsByGroup}
hoveredMappingKeys={hoveredMappingKeys}
onSetHoveredMappingKeys={setHoveredMappingKeys}
setCardWrapperRef={setCardWrapperRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ export const EditModal = ({
const hasCurrentLocations = viewModel.currentLocations.length > 0;
const hasNewLocation = viewModel.newLocation.id !== '';

const [selectedLocationIds, setSelectedLocationIds] = useState<string[]>([]);
const [selectedLocationIds, setSelectedLocationIds] = useState<string[]>(() =>
viewModel.currentLocations.map((l) => l.id)
);

const [selectedFieldIds, setSelectedFieldIds] = useState(
() => viewModel.newLocation.selectedFieldIds ?? []
Expand All @@ -64,7 +66,7 @@ export const EditModal = ({
// Do not depend on `viewModel.newLocation` reference (it can churn without semantic change).
useEffect(() => {
if (!isOpen) return;
setSelectedLocationIds([]);
setSelectedLocationIds(viewModel.currentLocations.map((l) => l.id));
setSelectedFieldIds(viewModel.newLocation.selectedFieldIds ?? []);
setDestinationFieldState(null);
// eslint-disable-next-line react-hooks/exhaustive-deps -- sync from props only on open / row-id set change, not array identity
Expand Down
8 changes: 8 additions & 0 deletions apps/google-docs/src/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@

// Only configure if we're in a DOM environment (not Node.js)
// This file is for frontend tests only - function tests use functions/vitest.config.mts
if (typeof window !== 'undefined' && typeof ResizeObserver === 'undefined') {
window.ResizeObserver = class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
};
}

if (typeof window !== 'undefined') {
try {
// Import jest-dom and matchers only in browser/DOM environment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,28 +113,29 @@ describe('EditModal', () => {
.filter((element) => element.getAttribute('aria-pressed') !== null);
const [summaryCard, descriptionCard] = locationCards;

expect(summaryCard).toHaveAttribute('aria-pressed', 'false');
expect(descriptionCard).toHaveAttribute('aria-pressed', 'false');
// All current locations start pre-selected
expect(summaryCard).toHaveAttribute('aria-pressed', 'true');
expect(descriptionCard).toHaveAttribute('aria-pressed', 'true');

fireEvent.click(summaryCard);

await waitFor(() => {
expect(summaryCard).toHaveAttribute('aria-pressed', 'true');
expect(descriptionCard).toHaveAttribute('aria-pressed', 'false');
expect(summaryCard).toHaveAttribute('aria-pressed', 'false');
expect(descriptionCard).toHaveAttribute('aria-pressed', 'true');
});

fireEvent.click(descriptionCard);
fireEvent.click(summaryCard);

await waitFor(() => {
expect(summaryCard).toHaveAttribute('aria-pressed', 'true');
expect(descriptionCard).toHaveAttribute('aria-pressed', 'true');
});

fireEvent.click(summaryCard);
fireEvent.click(descriptionCard);

await waitFor(() => {
expect(summaryCard).toHaveAttribute('aria-pressed', 'false');
expect(descriptionCard).toHaveAttribute('aria-pressed', 'true');
expect(summaryCard).toHaveAttribute('aria-pressed', 'true');
expect(descriptionCard).toHaveAttribute('aria-pressed', 'false');
});
});

Expand Down Expand Up @@ -190,15 +191,18 @@ describe('EditModal', () => {
expect(
screen.queryByText('No destination entry is available for the entry currently in view.')
).toBeNull();
expect(screen.getByRole('button', { name: 'Exclude content' })).toBeDisabled();
// Button starts enabled because all current locations are pre-selected
expect(screen.getByRole('button', { name: 'Exclude content' })).not.toBeDisabled();

const locationCards = screen
.getAllByRole('button')
.filter((el) => el.getAttribute('aria-pressed') !== null);
fireEvent.click(locationCards[0]);

// Deselect all locations — button should disable
locationCards.forEach((card) => fireEvent.click(card));

await waitFor(() => {
expect(screen.getByRole('button', { name: 'Exclude content' })).not.toBeDisabled();
expect(screen.getByRole('button', { name: 'Exclude content' })).toBeDisabled();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,12 @@ describe('MappingView', () => {
});

const { container } = render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

expect(container.querySelectorAll('[data-testid^="mapping-card-"]')).toHaveLength(1);
Expand Down Expand Up @@ -335,7 +340,12 @@ describe('MappingView', () => {
});

const { container } = render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

expect(container.querySelectorAll('[data-testid^="mapping-card-"]')).toHaveLength(1);
Expand Down Expand Up @@ -368,7 +378,12 @@ describe('MappingView', () => {
});

render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

expect(screen.getByText('Body copy (1/2)')).toBeTruthy();
Expand Down Expand Up @@ -433,7 +448,12 @@ describe('MappingView', () => {
});

const { container } = render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

expect(screen.getByText('Body copy (1/3)')).toBeTruthy();
Expand Down Expand Up @@ -549,7 +569,12 @@ describe('MappingView', () => {
});

const { container } = render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

expect(screen.getAllByText('Body copy').length).toBeGreaterThan(0);
Expand Down Expand Up @@ -650,7 +675,12 @@ describe('MappingView', () => {
});

const { container } = render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

expect(container.querySelectorAll('[data-testid^="mapping-group-surface-"]')).toHaveLength(0);
Expand Down Expand Up @@ -683,7 +713,12 @@ describe('MappingView', () => {
});

const { container } = render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

const textSegments = Array.from(
Expand Down Expand Up @@ -731,7 +766,12 @@ describe('MappingView', () => {
});

const { container, rerender } = render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);
const groupedTextSegments = container.querySelectorAll('[data-review-text-segment="true"]');
const selectedRange = createDomRange(groupedTextSegments[1].firstChild as Text, 0, 6);
Expand All @@ -744,7 +784,12 @@ describe('MappingView', () => {
});

rerender(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

fireEvent.click(screen.getByRole('button', { name: 'Reassign' }));
Expand Down Expand Up @@ -775,7 +820,12 @@ describe('MappingView', () => {

const payload = createPayload();
render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

fireEvent.click(screen.getByRole('button', { name: 'Assign' }));
Expand Down Expand Up @@ -803,6 +853,7 @@ describe('MappingView', () => {
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={null}
mode="edit"
/>
);

Expand All @@ -821,7 +872,12 @@ describe('MappingView', () => {

const payload = createPayload();
const { container, rerender } = render(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);
const selectedRange = createDomRange(
container.querySelector('[data-review-text-segment="true"]')?.firstChild as Text,
Expand All @@ -835,7 +891,12 @@ describe('MappingView', () => {
clearSelection: mockClearSelection,
});
rerender(
<MappingView payload={payload} {...mappingViewGraphProps(payload)} selectedEntryIndex={0} />
<MappingView
payload={payload}
{...mappingViewGraphProps(payload)}
selectedEntryIndex={0}
mode="edit"
/>
);

fireEvent.click(screen.getByRole('button', { name: 'Exclude' }));
Expand Down Expand Up @@ -886,6 +947,7 @@ describe('MappingView', () => {
entryBlockGraph={currentGraph}
onEntryBlockGraphChange={onEntryBlockGraphChange}
selectedEntryIndex={0}
mode="edit"
/>
);

Expand All @@ -910,6 +972,7 @@ describe('MappingView', () => {
entryBlockGraph={currentGraph}
onEntryBlockGraphChange={onEntryBlockGraphChange}
selectedEntryIndex={0}
mode="edit"
/>
);

Expand All @@ -923,9 +986,9 @@ describe('MappingView', () => {
)
).toBeNull();

const locationCard = document.querySelector('button[aria-pressed]') as HTMLElement;
// Location is pre-selected — confirm button is already enabled
const locationCard = document.querySelector('button[aria-pressed="true"]') as HTMLElement;
expect(locationCard).toBeTruthy();
fireEvent.click(locationCard);

const confirmButton = screen.getAllByRole('button', { name: 'Exclude content' }).at(-1);
expect(confirmButton).toBeTruthy();
Expand All @@ -939,6 +1002,7 @@ describe('MappingView', () => {
entryBlockGraph={currentGraph}
onEntryBlockGraphChange={onEntryBlockGraphChange}
selectedEntryIndex={0}
mode="edit"
/>
);

Expand All @@ -958,6 +1022,7 @@ describe('MappingView', () => {
entryBlockGraph={payload.entryBlockGraph}
onEntryBlockGraphChange={onEntryBlockGraphChange}
selectedEntryIndex={1}
mode="edit"
/>
);

Expand Down
Loading