-
Notifications
You must be signed in to change notification settings - Fork 992
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16209 from martenson/more-pages
bring grids for (published) pages on par with workflows
- Loading branch information
Showing
25 changed files
with
1,391 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { expect, jest } from "@jest/globals"; | ||
|
||
import { mount, shallowMount, createWrapper } from "@vue/test-utils"; | ||
import { getLocalVue } from "tests/jest/helpers"; | ||
import { PiniaVuePlugin, createPinia } from "pinia"; | ||
import { createTestingPinia } from "@pinia/testing"; | ||
import { mockFetcher } from "@/schema/__mocks__"; | ||
import { useUserStore } from "@/stores/userStore"; | ||
|
||
import PageDropdown from "./PageDropdown.vue"; | ||
|
||
import "jest-location-mock"; | ||
|
||
jest.mock("@/schema"); | ||
|
||
const waitRAF = () => new Promise((resolve) => requestAnimationFrame(resolve)); | ||
|
||
const localVue = getLocalVue(true); | ||
localVue.use(PiniaVuePlugin); | ||
|
||
const PAGE_DATA_OWNED = { | ||
id: "page1235", | ||
title: "My Page Title", | ||
description: "A description derived from an annotation.", | ||
shared: false, | ||
}; | ||
|
||
const PAGE_DATA_SHARED = { | ||
id: "page1235", | ||
title: "My Page Title", | ||
description: "A description derived from an annotation.", | ||
shared: true, | ||
}; | ||
|
||
describe("PageDropdown.vue", () => { | ||
let wrapper: any; | ||
|
||
function pageOptions() { | ||
return wrapper.findAll(".dropdown-menu .dropdown-item"); | ||
} | ||
|
||
describe("navigation on owned pages", () => { | ||
beforeEach(async () => { | ||
const pinia = createPinia(); | ||
const propsData = { | ||
root: "/rootprefix/", | ||
page: PAGE_DATA_OWNED, | ||
}; | ||
wrapper = shallowMount(PageDropdown, { | ||
propsData, | ||
localVue, | ||
pinia: pinia, | ||
}); | ||
const userStore = useUserStore(); | ||
userStore.currentUser = { email: "my@email", id: "1", tags_used: [] }; | ||
}); | ||
|
||
it("should show page title", async () => { | ||
const titleWrapper = await wrapper.find(".page-title"); | ||
expect(titleWrapper.text()).toBe("My Page Title"); | ||
}); | ||
|
||
it("should decorate dropdown with page ID for automation", async () => { | ||
const linkWrapper = await wrapper.find("[data-page-dropdown='page1235']"); | ||
expect(linkWrapper.exists()).toBeTruthy(); | ||
}); | ||
|
||
it("should have a 'Share' option", async () => { | ||
expect(wrapper.find(".dropdown-menu .dropdown-item-share").exists()).toBeTruthy(); | ||
}); | ||
|
||
it("should provide 5 options", () => { | ||
expect(pageOptions().length).toBe(5); | ||
}); | ||
}); | ||
|
||
describe("navigation on shared pages", () => { | ||
beforeEach(async () => { | ||
const propsData = { | ||
root: "/rootprefixshared/", | ||
page: PAGE_DATA_SHARED, | ||
}; | ||
wrapper = shallowMount(PageDropdown, { | ||
propsData, | ||
localVue, | ||
pinia: createTestingPinia(), | ||
}); | ||
}); | ||
|
||
it("should have the 'View' option", async () => { | ||
expect(wrapper.find(".dropdown-menu .dropdown-item-view").exists()).toBeTruthy(); | ||
}); | ||
|
||
it("should have only single option", () => { | ||
expect(pageOptions().length).toBe(1); | ||
}); | ||
}); | ||
|
||
describe("clicking page deletion on owned page", () => { | ||
const pinia = createPinia(); | ||
|
||
async function mountAndDelete() { | ||
const propsData = { | ||
root: "/rootprefixdelete/", | ||
page: PAGE_DATA_OWNED, | ||
}; | ||
wrapper = mount(PageDropdown, { | ||
propsData, | ||
localVue, | ||
pinia: pinia, | ||
stubs: { | ||
transition: false, | ||
}, | ||
}); | ||
const userStore = useUserStore(); | ||
userStore.currentUser = { email: "my@email", id: "1", tags_used: [] }; | ||
wrapper.find(".page-dropdown").trigger("click"); | ||
await wrapper.vm.$nextTick(); | ||
wrapper.find(".dropdown-item-delete").trigger("click"); | ||
// this is here because b-modal is lazy loading and portalling | ||
// see https://github.com/bootstrap-vue/bootstrap-vue/blob/dev/src/components/modal/modal.spec.js#L233 | ||
await wrapper.vm.$nextTick(); | ||
await waitRAF(); | ||
await wrapper.vm.$nextTick(); | ||
await waitRAF(); | ||
await wrapper.vm.$nextTick(); | ||
await waitRAF(); | ||
const foot: any = document.getElementById("delete-page-modal-page1235___BV_modal_footer_"); | ||
createWrapper(foot).find(".btn-primary").trigger("click"); | ||
await wrapper.vm.$nextTick(); | ||
await waitRAF(); | ||
await wrapper.vm.$nextTick(); | ||
await waitRAF(); | ||
} | ||
|
||
afterEach(() => { | ||
mockFetcher.clearMocks(); | ||
}); | ||
|
||
it("should fire deletion API request upon confirmation", async () => { | ||
mockFetcher.path("/api/pages/{id}").method("delete").mock({ status: 204 }); | ||
await mountAndDelete(); | ||
const emitted = wrapper.emitted(); | ||
expect(emitted["onRemove"][0][0]).toEqual("page1235"); | ||
expect(emitted["onSuccess"]).toBeTruthy(); | ||
}); | ||
|
||
it("should not fire deletion API request if not confirmed", async () => { | ||
await mountAndDelete(); | ||
const emitted = wrapper.emitted(); | ||
expect(emitted["onRemove"]).toBeFalsy(); | ||
expect(emitted["onSuccess"]).toBeFalsy(); | ||
}); | ||
|
||
it("should emit an error on API fail", async () => { | ||
mockFetcher | ||
.path("/api/pages/{id}") | ||
.method("delete") | ||
.mock(() => { | ||
throw Error("mock error"); | ||
}); | ||
await mountAndDelete(); | ||
const emitted = wrapper.emitted(); | ||
expect(emitted["onError"]).toBeTruthy(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<script setup lang="ts"> | ||
import _l from "@/utils/localization"; | ||
import { computed, unref } from "vue"; | ||
import { deletePage } from "./services"; | ||
import { storeToRefs } from "pinia"; | ||
import { useUserStore } from "@/stores/userStore"; | ||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; | ||
import { faCaretDown } from "@fortawesome/free-solid-svg-icons"; | ||
import { library } from "@fortawesome/fontawesome-svg-core"; | ||
library.add(faCaretDown); | ||
interface Page { | ||
id: string; | ||
shared?: Boolean; | ||
title?: string; | ||
description?: string; | ||
} | ||
interface PageDropdownProps { | ||
page: Page; | ||
root: string; | ||
published?: Boolean; | ||
} | ||
const props = defineProps<PageDropdownProps>(); | ||
const { isAnonymous } = storeToRefs(useUserStore()); | ||
const emit = defineEmits(["onRemove", "onSuccess", "onError"]); | ||
const urlEdit = computed(() => `${props.root}pages/editor?id=${props.page.id}`); | ||
const urlEditAttributes = computed(() => `${props.root}pages/edit?id=${props.page.id}`); | ||
const urlShare = computed(() => `${props.root}pages/sharing?id=${props.page.id}`); | ||
const urlView = computed(() => `${props.root}published/page?id=${props.page.id}`); | ||
const readOnly = computed(() => props.page.shared || props.published || unref(isAnonymous)); | ||
function onDelete(page_id: string) { | ||
deletePage(page_id) | ||
.then((response) => { | ||
emit("onRemove", page_id); | ||
emit("onSuccess"); | ||
}) | ||
.catch((error) => { | ||
emit("onError", error); | ||
}); | ||
} | ||
</script> | ||
<template> | ||
<div> | ||
<b-link | ||
class="page-dropdown" | ||
data-toggle="dropdown" | ||
aria-haspopup="true" | ||
:data-page-dropdown="props.page.id" | ||
aria-expanded="false"> | ||
<font-awesome-icon icon="caret-down" class="fa-lg" /> | ||
<span class="page-title">{{ props.page.title }}</span> | ||
</b-link> | ||
<p v-if="props.page.description">{{ props.page.description }}</p> | ||
<div class="dropdown-menu" aria-labelledby="page-dropdown"> | ||
<a class="dropdown-item dropdown-item-view" :href="urlView"> | ||
<span class="fa fa-eye fa-fw mr-1" /> | ||
<span>View</span> | ||
</a> | ||
<a v-if="!readOnly" class="dropdown-item dropdown-item-edit" :href="urlEdit"> | ||
<span class="fa fa-edit fa-fw mr-1" /> | ||
<span>Edit content</span> | ||
</a> | ||
<a v-if="!readOnly" class="dropdown-item dropdown-item-attributes" :href="urlEditAttributes"> | ||
<span class="fa fa-pencil fa-fw mr-1" /> | ||
<span>Edit attributes</span> | ||
</a> | ||
<a v-if="!readOnly" class="dropdown-item dropdown-item-share" :href="urlShare"> | ||
<span class="fa fa-share-alt fa-fw mr-1" /> | ||
<span>Control sharing</span> | ||
</a> | ||
<a | ||
v-if="!readOnly" | ||
v-b-modal="`delete-page-modal-${props.page.id}`" | ||
class="dropdown-item dropdown-item-delete"> | ||
<span class="fa fa-trash fa-fw mr-1" /> | ||
<span>Delete</span> | ||
</a> | ||
<b-modal | ||
:id="`delete-page-modal-${props.page.id}`" | ||
hide-backdrop | ||
title="Confirm page deletion" | ||
title-tag="h2" | ||
@ok="onDelete(props.page.id)"> | ||
<p v-localize>Really delete the page titled: "{{ props.page.title }}"?</p> | ||
</b-modal> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import PageIndexActions from "./PageIndexActions.vue"; | ||
import { shallowMount } from "@vue/test-utils"; | ||
import { getLocalVue } from "tests/jest/helpers"; | ||
|
||
import "jest-location-mock"; | ||
|
||
const localVue = getLocalVue(); | ||
|
||
describe("PageIndexActions.vue", () => { | ||
let wrapper: any; | ||
const mockRouter = { | ||
push: jest.fn(), | ||
}; | ||
|
||
beforeEach(async () => { | ||
wrapper = shallowMount(PageIndexActions, { | ||
mocks: { | ||
$router: mockRouter, | ||
}, | ||
localVue, | ||
}); | ||
}); | ||
|
||
describe("navigation", () => { | ||
it("should create a page when create is clicked", async () => { | ||
await wrapper.find("#page-create").trigger("click"); | ||
expect(mockRouter.push).toHaveBeenCalledTimes(1); | ||
expect(mockRouter.push).toHaveBeenCalledWith("/pages/create"); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<script setup lang="ts"> | ||
import { BButton } from "bootstrap-vue"; | ||
import { useRouter } from "vue-router/composables"; | ||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; | ||
import { faPlus } from "@fortawesome/free-solid-svg-icons"; | ||
import { library } from "@fortawesome/fontawesome-svg-core"; | ||
const router = useRouter(); | ||
library.add(faPlus); | ||
function create() { | ||
router.push("/pages/create"); | ||
} | ||
</script> | ||
<template> | ||
<span> | ||
<b-button id="page-create" class="m-1" @click="create"> | ||
<font-awesome-icon icon="plus" /> | ||
{{ "Create" | localize }} | ||
</b-button> | ||
</span> | ||
</template> |
Oops, something went wrong.