Skip to content

Commit

Permalink
N21-2031 Placeholder element for deleted tools in boards (#3363)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinOehlerkingCap authored Aug 22, 2024
1 parent 85a4120 commit 6178f20
Show file tree
Hide file tree
Showing 18 changed files with 573 additions and 191 deletions.
3 changes: 3 additions & 0 deletions config/webpack/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ module.exports = {
"@feature-board-collaborative-text-editor-element": getDir(
"src/modules/feature/board-collaborative-text-editor-element"
),
"@feature-board-deleted-element": getDir(
"src/modules/feature/board-deleted-element"
),
"@feature-course-sync": getDir("src/modules/feature/course-sync"),
"@feature-board": getDir("src/modules/feature/board"),
"@feature-editor": getDir("src/modules/feature/editor"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<script setup lang="ts">
import { ExternalToolConfigurationTemplate } from "@data-external-tool";
import { defineProps, PropType } from "vue";
import { PropType } from "vue";
defineProps({
item: {
Expand Down
3 changes: 3 additions & 0 deletions src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ export default {
"components.board.menu.fileElement": "Datei-Einstellungen",
"components.board.menu.linkElement": "Link-Einstellungen",
"components.board.menu.submissionElement": "Abgabe-Einstellungen",
"components.board.menu.deletedElement": "Gelöschter-Inhalt-Einstellungen",
"components.board.notifications.errors.fileNameExists":
"Es existiert bereits eine Datei mit diesem Namen.",
"components.board.notifications.errors.fileServiceNotAvailable":
Expand Down Expand Up @@ -421,6 +422,8 @@ export default {
"components.cardElement.titleElement.validation.required":
"Bitte Titel angeben.",
"components.cardElement.titleElement": "Titelelement",
"components.cardElement.deletedElement.warning.externalToolElement":
"Tool {toolName} nicht verfügbar. Bitte an Schuladministrator:in wenden.",
"components.datePicker.validation.format":
"Bitte Format DD.MM.YYYY verwenden.",
"components.datePicker.validation.required": "Bitte Datum angeben",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export default {
"components.board.menu.fileElement": "File settings",
"components.board.menu.linkElement": "Link settings",
"components.board.menu.submissionElement": "Submission settings",
"components.board.menu.deletedElement": "Deleted content settings",
"components.board.notifications.errors.fileNameExists":
"A file with this name already exists.",
"components.board.notifications.errors.fileServiceNotAvailable":
Expand Down Expand Up @@ -418,6 +419,8 @@ export default {
"components.cardElement.titleElement.validation.required":
"Please enter a title.",
"components.cardElement.titleElement": "Title element",
"components.cardElement.deletedElement.warning.externalToolElement":
"Tool {toolName} not available. Please contact the school administrator.",
"components.datePicker.validation.format": "Please use format DD.MM.YYYY",
"components.datePicker.validation.required": "Please enter a date.",
"components.dateTimePicker.messages.dateInPast":
Expand Down
4 changes: 4 additions & 0 deletions src/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ export default {
"components.board.menu.fileElement": "Configuración del archivo",
"components.board.menu.linkElement": "Configuración del enlace",
"components.board.menu.submissionElement": "Configuración del envío",
"components.board.menu.deletedElement":
"Configuración de contenido eliminado",
"components.board.notifications.errors.fileNameExists":
"Ya existe un archivo con este nombre.",
"components.board.notifications.errors.fileServiceNotAvailable":
Expand Down Expand Up @@ -425,6 +427,8 @@ export default {
"components.cardElement.titleElement.validation.required":
"Por favor ingrese un título.",
"components.cardElement.titleElement": "Elemento título",
"components.cardElement.deletedElement.warning.externalToolElement":
"La herramienta {toolName} no está disponible. Por favor comuníquese con el administrador de la escuela.",
"components.datePicker.validation.format":
"Por favor utilice el formato DD.MM.YYYY",
"components.datePicker.validation.required": "Por favor ingrese una fecha.",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/uk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ export default {
"components.board.menu.fileElement": "Налаштування файлу",
"components.board.menu.linkElement": "Налаштування посилання",
"components.board.menu.submissionElement": "Налаштування Подання",
"components.board.menu.deletedElement": "Видалені налаштування вмісту",
"components.board.notifications.errors.fileNameExists":
"Файл з такою назвою вже існує.",
"components.board.notifications.errors.fileServiceNotAvailable":
Expand Down Expand Up @@ -427,6 +428,8 @@ export default {
"components.cardElement.titleElement.validation.required":
"Будь ласка, введіть назву.",
"components.cardElement.titleElement": "Елемент заголовка",
"components.cardElement.deletedElement.warning.externalToolElement":
"Інструмент {toolName} недоступний. Будь ласка, зверніться до адміністратора школи.",
"components.datePicker.validation.format": "Використовуйте формат ДД.ММ.РРРР",
"components.datePicker.validation.required": "Будь ласка, введіть дату.",
"components.dateTimePicker.messages.dateInPast": "Дата і час у минулому.",
Expand Down
188 changes: 188 additions & 0 deletions src/modules/feature/board-deleted-element/DeletedElement.unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { ContentElementType, DeletedElementResponse } from "@/serverApi/v3";
import { timestampsResponseFactory } from "@@/tests/test-utils";
import {
createTestingI18n,
createTestingVuetify,
} from "@@/tests/test-utils/setup";
import { useBoardFocusHandler, useBoardPermissions } from "@data-board";
import { createMock, DeepMocked } from "@golevelup/ts-jest";
import { WarningAlert } from "@ui-alert";
import { mount } from "@vue/test-utils";
import { nextTick } from "vue";
import { ComponentProps } from "vue-component-type-helpers";
import DeletedElement from "./DeletedElement.vue";
import DeletedElementMenu from "./DeletedElementMenu.vue";

jest.mock("@data-board");

const DELETED_ELEMENT: DeletedElementResponse = {
id: "deleted-element-id",
content: {
deletedElementType: ContentElementType.ExternalTool,
title: "Deleted Tool",
},
type: ContentElementType.Deleted,
timestamps: timestampsResponseFactory.build(),
};

describe("DeletedElement", () => {
let useBoardFocusHandlerMock: DeepMocked<
ReturnType<typeof useBoardFocusHandler>
>;
let useBoardPermissionsMock: DeepMocked<
ReturnType<typeof useBoardPermissions>
>;

beforeEach(() => {
useBoardFocusHandlerMock =
createMock<ReturnType<typeof useBoardFocusHandler>>();
useBoardPermissionsMock = createMock<
ReturnType<typeof useBoardPermissions>
>({ isTeacher: true });

jest.mocked(useBoardFocusHandler).mockReturnValue(useBoardFocusHandlerMock);
jest.mocked(useBoardPermissions).mockReturnValue(useBoardPermissionsMock);
});

afterEach(() => {
jest.clearAllMocks();
});

const getWrapper = (
props: ComponentProps<typeof DeletedElement> = {
element: DELETED_ELEMENT,
isEditMode: false,
}
) => {
const wrapper = mount(DeletedElement, {
global: {
plugins: [createTestingVuetify(), createTestingI18n()],
},
props,
});

return {
wrapper,
};
};

afterEach(() => {
jest.resetAllMocks();
});

describe("when the user is not a teacher", () => {
const setup = () => {
useBoardPermissionsMock.isTeacher = false;

const { wrapper } = getWrapper({
element: DELETED_ELEMENT,
isEditMode: true,
});

return {
wrapper,
};
};

it("should not show the element", async () => {
const { wrapper } = setup();

const deletedElement = wrapper.findComponent({ ref: "deletedElement" });

expect(deletedElement.isVisible()).toEqual(false);
});
});

describe("Menu", () => {
describe("when in edit mode", () => {
const setup = () => {
const { wrapper } = getWrapper({
element: DELETED_ELEMENT,
isEditMode: true,
});

return {
wrapper,
};
};

it("should display the tree dot menu", async () => {
const { wrapper } = setup();

const threeDotMenu = wrapper.findComponent(DeletedElementMenu);

expect(threeDotMenu.isVisible()).toEqual(true);
});
});

describe("when not in edit mode", () => {
const setup = () => {
const { wrapper } = getWrapper({
element: DELETED_ELEMENT,
isEditMode: false,
});

return {
wrapper,
};
};

it("should not display the tree dot menu", async () => {
const { wrapper } = setup();

const threeDotMenu = wrapper.findComponent(DeletedElementMenu);

expect(threeDotMenu.exists()).toEqual(false);
});
});

describe("when deleting the element", () => {
const setup = () => {
const { wrapper } = getWrapper({
element: DELETED_ELEMENT,
isEditMode: true,
});

return {
wrapper,
};
};

it("should emit an event", async () => {
const { wrapper } = setup();

wrapper.findComponent(DeletedElementMenu).vm.$emit("delete:element");
await nextTick();

expect(wrapper.emitted("delete:element")).toEqual([
[DELETED_ELEMENT.id],
]);
});
});
});

describe("Alert", () => {
describe("when the deleted element was an external tool element", () => {
const setup = () => {
const { wrapper } = getWrapper({
element: DELETED_ELEMENT,
isEditMode: true,
});

return {
wrapper,
};
};

it("should display a warning", async () => {
const { wrapper } = setup();

const alert = wrapper.findComponent(WarningAlert);

expect(alert.text()).toEqual(
"components.cardElement.deletedElement.warning.externalToolElement"
);
});
});
});
});
71 changes: 71 additions & 0 deletions src/modules/feature/board-deleted-element/DeletedElement.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<template>
<VCard
v-show="isTeacher"
class="mb-4"
data-testid="board-deleted-element"
elevation="0"
variant="outlined"
ref="deletedElement"
:ripple="false"
>
<WarningAlert
v-if="
element.content.deletedElementType === ContentElementType.ExternalTool
"
>
{{
$t(
"components.cardElement.deletedElement.warning.externalToolElement",
{
toolName: element.content.title,
}
)
}}
</WarningAlert>
<ContentElementBar :has-grey-background="true" :icon="mdiPuzzleOutline">
<template #title>
{{ element.content.title }}
</template>
<template #menu>
<DeletedElementMenu
v-if="isEditMode"
@delete:element="onDeleteElement"
/>
</template>
</ContentElementBar>
</VCard>
</template>

<script setup lang="ts">
import { ContentElementType, DeletedElementResponse } from "@/serverApi/v3";
import { useBoardFocusHandler, useBoardPermissions } from "@data-board";
import { mdiPuzzleOutline } from "@mdi/js";
import { WarningAlert } from "@ui-alert";
import { ContentElementBar } from "@ui-board";
import { PropType, Ref, ref, toRef } from "vue";
import DeletedElementMenu from "./DeletedElementMenu.vue";
const props = defineProps({
element: {
type: Object as PropType<DeletedElementResponse>,
required: true,
},
isEditMode: { type: Boolean, required: true },
});
const emit = defineEmits<{
(e: "delete:element", elementId: string): void;
}>();
const { isTeacher } = useBoardPermissions();
const autofocus: Ref<boolean> = ref(false);
const element: Ref<DeletedElementResponse> = toRef(props, "element");
useBoardFocusHandler(element.value.id, ref(null), () => {
autofocus.value = true;
});
const onDeleteElement = () => {
emit("delete:element", element.value.id);
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
createTestingI18n,
createTestingVuetify,
} from "@@/tests/test-utils/setup";
import { BoardMenuActionDelete } from "@ui-board";
import { shallowMount } from "@vue/test-utils";
import DeletedElementMenu from "./DeletedElementMenu.vue";

describe("DeletedElementMenu", () => {
const getWrapper = () => {
document.body.setAttribute("data-app", "true");

const wrapper = shallowMount(DeletedElementMenu, {
global: {
plugins: [createTestingVuetify(), createTestingI18n()],
},
});

return {
wrapper,
};
};

afterEach(() => {
jest.resetAllMocks();
});

describe("Delete Button", () => {
it("should have a menu option to delete", () => {
const { wrapper } = getWrapper();

const menuItem = wrapper.findComponent(BoardMenuActionDelete);

expect(menuItem.exists()).toEqual(true);
});

it("should emit the delete event on click", async () => {
const { wrapper } = getWrapper();

const menuItem = wrapper.findComponent(BoardMenuActionDelete);

await menuItem.trigger("click");

expect(wrapper.emitted("delete:element")).toBeDefined();
});
});
});
Loading

0 comments on commit 6178f20

Please sign in to comment.