Skip to content

Commit

Permalink
"Create + Another" card starts with previous card's structure (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
jxjj authored Jan 14, 2025
1 parent e40e57a commit 782ee14
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 15 deletions.
4 changes: 2 additions & 2 deletions resources/client/components/BlockEditor/HintBlockInput.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<template>
<div data-cy="hint-block-input">
<Label :for="makeInputId('hidden-text')" class="sr-only">Hint</Label>
<Label :for="makeInputId('hint-text')" class="sr-only">Hint Text</Label>
<InputGroup
:id="makeInputId('hidden-text')"
:id="makeInputId('hint-text')"
label="Hidden Text"
:labelHidden="true"
:modelValue="modelValue"
Expand Down
56 changes: 56 additions & 0 deletions resources/client/lib/makeContentBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ContentBlock, ContentBlockType } from "@/types";
import {
isAudioBlock,
isEmbedBlock,
isHintBlock,
isImageBlock,
isMathBlock,
isTextBlock,
isVideoBlock,
} from "./isBlockOfType";

/**
* makes an empty content block of a particular type
*/
export function makeContentBlock(type: ContentBlockType) {
const block: ContentBlock = {
id: crypto.randomUUID(),
type,
content: "",
meta: null,
};

if (isImageBlock(block)) {
block.meta = { alt: "" };
return block;
}

if (isHintBlock(block)) {
block.meta = { label: "Hint" };
return block;
}

if (isTextBlock(block)) {
block.meta = { lang: null };
return block;
}

if (isAudioBlock(block)) {
// using `isBlockOfType()` type guards to return the correct block type
return block;
}

if (isVideoBlock(block)) {
return block;
}

if (isEmbedBlock(block)) {
return block;
}

if (isMathBlock(block)) {
return block;
}

throw new Error(`Unknown block type: ${type}`);
}
31 changes: 18 additions & 13 deletions resources/client/pages/Cards/CreateOrEditCardPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import { Button } from "@/components/ui/button";
import PageTitle from "@/components/PageTitle.vue";
import PageSubtitle from "@/components/PageSubtitle.vue";
import { useIsDeckTTSEnabled } from "@/composables/useIsDeckTTSEnabled";
import { makeContentBlock } from "@/lib/makeContentBlock";
import { IS_DECK_TTS_ENABLED_INJECTION_KEY } from "@/constants";
const props = defineProps<{
Expand All @@ -101,23 +102,14 @@ const cardIdRef = computed(() => props.cardId ?? null);
const { data: deck } = useDeckByIdQuery(deckIdRef);
const { data: card } = useCardByIdQuery(cardIdRef);
function createTextContentBlock(): T.TextContentBlock {
return {
id: crypto.randomUUID(),
type: "text",
content: "",
meta: null,
};
}
onMounted(() => {
if (!isCreateMode.value) {
return;
}
// add two text content blocks
form.front = [createTextContentBlock()];
form.back = [createTextContentBlock()];
form.front = [makeContentBlock("text")];
form.back = [makeContentBlock("text")];
});
watch(
Expand Down Expand Up @@ -154,10 +146,23 @@ function handleSave({ saveAndAddAnother = false } = {}) {
form.back = removeEmptyBlocks(form.back);
form.front = removeEmptyBlocks(form.front);
// snapshot the types of the front and back blocks before saving
// so that we can use them as the default for the next card
// if user clicks "Create + Another"
const frontTypes = form.front.map((block) => block.type);
const backTypes = form.back.map((block) => block.type);
const onSuccess = () => {
if (saveAndAddAnother) {
form.front = [createTextContentBlock()];
form.back = [createTextContentBlock()];
// when saving and adding another, use the previous card's front
// and back types as the default for the new card. If there are no types
// (e.g. the user deleted all content), default to text
form.front = frontTypes.length
? frontTypes.map(makeContentBlock)
: [makeContentBlock("text")];
form.back = backTypes.length
? backTypes.map(makeContentBlock)
: [makeContentBlock("text")];
return;
}
Expand Down
66 changes: 66 additions & 0 deletions tests/cypress/e2e/deckShowPage.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,70 @@ describe("DeckShowPage", () => {
cy.get('[data-cy="flippable-card"]').should("have.length", 1);
cy.contains("Another card");
});

it("'Create and Add Another' button uses previous card's structure", () => {
// setup intercept for creating a card
cy.intercept("POST", "/api/decks/*/cards").as("createCard");

cy.visit(`/decks/${deckId}/cards/create`);

// set up aliases
cy.get('[data-cy="front-side-input"]').as("frontSideInput");
cy.get('[data-cy="back-side-input"]').as("backSideInput");

// remove old front-side block
cy.get("@frontSideInput").within(() => {
cy.get('[data-cy="remove-content-block-button"]').click();
});

// add a new image block
cy.get("@frontSideInput").contains("Add Block").click();
cy.get("[role='menu']").contains("Image").click();
cy.get("@frontSideInput").within(() => {
cy.getInputByLabel("Image Url").type(
"https://upload.wikimedia.org/wikipedia/commons/4/41/Weisman_Art_Museum.jpg",
);
});

// add a hint block to front side
cy.get("@frontSideInput").contains("Add Block").click();
cy.get("[role='menu']").contains("Hint").click();
cy.get("@frontSideInput").within(() => {
cy.getInputByLabel("Hint Text").type("This is a hint");
});

// remove the default text block from back side
cy.get("@backSideInput").within(() => {
cy.get('[data-cy="remove-content-block-button"]').click();
});

// add an image block to back side
cy.get("@backSideInput").contains("Add Block").click();
cy.get("[role='menu']").contains("Image").click();
cy.get("@backSideInput").within(() => {
cy.getInputByLabel("Image Url").type(
"https://upload.wikimedia.org/wikipedia/commons/5/59/Goldy_the_Gopher.jpg",
);
});

// CREATE AND ADD ANOTHER
cy.contains("Create + Another").click();
cy.wait("@createCard");

cy.get("[data-cy='front-side-input']").within(() => {
// verify that the front side has the image block and hint block
cy.get("[data-cy='image-block-input']").should("exist");
cy.get("[data-cy='hint-block-input']").should("exist");

// and that it doesn't have a text block
cy.get("[data-cy='text-block-input-container']").should("not.exist");
});

cy.get("[data-cy='back-side-input']").within(() => {
// and that the back side has the image block
cy.get("[data-cy='image-block-input']").should("exist");
// and that it doesn't have a text block
cy.get("[data-cy='text-block-input-container']").should("not.exist");
});
});
});
9 changes: 9 additions & 0 deletions tests/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,12 @@ Cypress.Commands.add(
});
},
);

Cypress.Commands.add("getInputByLabel", (label: string) => {
return cy
.contains("label", label)
.invoke("attr", "for")
.then((id) => {
cy.get("#" + id);
});
});
5 changes: 5 additions & 0 deletions tests/cypress/support/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,10 @@ declare namespace Cypress {
deckId: number,
{ front, back }: { front: string; back: string },
): Chainable<any>;

/**
* Get the input element matching the label text.
*/
getInputByLabel(label: string): Chainable<any>;
}
}

0 comments on commit 782ee14

Please sign in to comment.