Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

N21-2031 Placeholder element for deleted tools in boards #3363

Merged
merged 9 commits into from
Aug 22, 2024
Merged
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
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
Loading