Skip to content
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
7 changes: 6 additions & 1 deletion components/review/Overlay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
v-model:show-details="showDetails"
v-model:show-discussion="showDiscussion"
:item="item"
:workspace-type="workspaceType"
@edit="props.onEdit"
@resolve="$emit('resolve')"
@back="emit('back')"
Expand All @@ -24,10 +25,14 @@

<script setup lang="ts">
import type { ReviewListItem } from '~/services/review';
import type { WorkspaceType } from '~/types/workspaces';

type RapidEditor = 'rapid' | 'rapid3';

interface Props {
item: ReviewListItem;
onEdit: () => void;
workspaceType: WorkspaceType;
onEdit: (editor?: RapidEditor) => void;
}

const props = defineProps<Props>();
Expand Down
43 changes: 40 additions & 3 deletions components/review/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,35 @@
:class="typeClass"
/><span class="d-none d-sm-inline fw-bold text-uppercase">{{ props.item.displayType }}:</span> #{{ props.item.id }}
</span>
<b-dropdown
v-if="isOsw && rapid3Available"
split
size="sm"
split-variant="primary"
variant="primary"
class="review-editor-dropdown"
teleport-to="body"
strategy="fixed"
@split-click="edit()"
>
<template #button-content>
<app-icon
variant="edit_location_alt"
no-margin
/>
<span class="d-none d-sm-inline ms-2">Edit Here</span>
</template>
<b-dropdown-item @click="edit('rapid')">
Rapid 2 (default)
</b-dropdown-item>
<b-dropdown-item @click="edit('rapid3')">
Rapid 3 (beta)
</b-dropdown-item>
</b-dropdown>
<button
v-else
class="btn btn-sm btn-primary"
@click="edit"
@click="edit()"
>
<app-icon
variant="edit_location_alt"
Expand Down Expand Up @@ -105,24 +131,31 @@
</template>

<script setup lang="ts">
import { rapid3Manager } from '~/services/index';
import type { ReviewListItem } from '~/services/review';
import type { WorkspaceType } from '~/types/workspaces';

type RapidEditor = 'rapid' | 'rapid3';

interface Props {
item: ReviewListItem;
workspaceType: WorkspaceType;
}

const props = defineProps<Props>();
const emit = defineEmits(['back', 'edit', 'resolve']);

const { isValidator } = useWorkspaceRole();
const rapid3Available = Boolean(rapid3Manager);
const isOsw = computed(() => props.workspaceType === 'osw');

const typeClass = computed(() => props.item.badgeClass.replace('bg-', 'text-'));

const showDetails = defineModel<boolean>('showDetails');
const showDiscussion = defineModel<boolean>('showDiscussion');

function edit() {
emit('edit');
function edit(editor: RapidEditor = 'rapid') {
emit('edit', editor);
}

function resolveChangeset() {
Expand Down Expand Up @@ -156,4 +189,8 @@ function toggleDiscussion() {
& { padding: 0.5rem; }
}
}

.review-editor-dropdown {
flex-shrink: 0;
}
</style>
8 changes: 6 additions & 2 deletions pages/workspace/[id]/review.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ shows the proper details in the map and attribute diff panel (playwright snapsho
<review-overlay
v-if="currentItem"
:item="currentItem"
:workspace-type="workspace.type"
@edit="openEditor"
@resolve="resolveCurrentChangeset"
@back="selectedItem = undefined"
Expand Down Expand Up @@ -297,7 +298,7 @@ async function resolveCurrentChangeset() {
}
}

async function openEditor() {
async function openEditor(editor: 'rapid' | 'rapid3' = 'rapid') {
if (!map.value) {
return;
}
Expand All @@ -306,7 +307,10 @@ async function openEditor() {

await navigateTo({
path: `/workspace/${workspaceId}/edit`,
query: { datatype: reviewList.workspace?.type },
query: {
datatype: reviewList.workspace?.type,
...(reviewList.workspace?.type === 'osw' ? { editor } : {})
},
hash: `#map=${zoom}/${lat}/${lon}`,
});
}
Expand Down
34 changes: 34 additions & 0 deletions test/e2e/review.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,13 +436,47 @@ test.describe('workspace review', () => {
}, { timeout: 30_000 }).toBe(true);

const url = new URL(page.url());
expect(url.searchParams.get('editor')).toBe('rapid');
// Verify that Edit Here targets the selected feedback.
const [zoom, lat, lon] = url.hash.slice('#map='.length).split('/').map(Number);
expect(zoom).toBeCloseTo(18);
expect(lat).toBeCloseTo(selectedFeedback.location_latitude);
expect(lon).toBeCloseTo(selectedFeedback.location_longitude);
});

test('selecting Rapid 3 opens that editor at the reviewed location', async ({ page }) => {
await seedAuthenticatedSession(page);
await stubReviewApis(page);
await page.route('https://ecn.*.tiles.virtualearth.net/**', route => route.abort());

await page.goto('/workspace/1/review');

const feedbackItem = sidebar(page)
.locator('.review-item', { hasText: 'This crossing is hard to navigate' });
await feedbackItem.click();

await expect(page.locator('.maplibregl-popup')).toContainText(selectedFeedback.feedback_text);
await expect(page.locator('.map-loading-overlay')).toBeHidden();

const editorDropdown = page.locator('.review-editor-dropdown');
await editorDropdown.locator('.dropdown-toggle').click();
await page.getByRole('menuitem', { name: 'Rapid 3 (beta)' }).click();

await expect.poll(() => {
const url = new URL(page.url());
return url.pathname === '/workspace/1/edit'
&& url.searchParams.get('datatype') === 'osw'
&& url.searchParams.get('editor') === 'rapid3'
&& /^#map=[^/]+\/[^/]+\/[^/]+$/.test(url.hash);
}, { timeout: 30_000 }).toBe(true);

const url = new URL(page.url());
const [zoom, lat, lon] = url.hash.slice('#map='.length).split('/').map(Number);
expect(zoom).toBeCloseTo(18);
expect(lat).toBeCloseTo(selectedFeedback.location_latitude);
expect(lon).toBeCloseTo(selectedFeedback.location_longitude);
});

// @test e2e: while the data is loading on the map a spinner appears (assert() this is true)
test('shows a spinner on the map while data is loading', async ({ page }) => {
await seedAuthenticatedSession(page);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
- navigation:
- text: "/Feedback: #\\d+/"
- button " Edit Here"
- button "Toggle dropdown"
- button ""
- button " Mark as Resolved"
- button " Mark as Resolved"
65 changes: 65 additions & 0 deletions test/unit/components/ReviewToolbar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// @vitest-environment nuxt

import { mountSuspended } from '@nuxt/test-utils/runtime';
import { describe, expect, it, vi } from 'vitest';
import ReviewToolbar from '~/components/review/Toolbar.vue';

import type { ReviewListItem } from '~/services/review';
import type { Workspace } from '~/types/workspaces';

vi.mock('~/services/index', () => ({
rapid3Manager: {}
}));

const item = {
id: 555,
badgeIcon: 'chat_bubble',
badgeClass: 'bg-danger',
displayType: 'Feedback',
isChangeset: false,
isFeedback: true,
isNote: false,
isResolved: false,
hasComments: false,
commentCount: 0,
needsReview: false
} as ReviewListItem;

const workspace = { role: 'lead' } as Workspace;

describe('ReviewToolbar', () => {
it('offers Rapid 2 and Rapid 3 for an OSW workspace', async () => {
const wrapper = await mountSuspended(ReviewToolbar, {
props: { item, workspaceType: 'osw' },
global: { provide: { workspace } }
});

expect(document.body.textContent).toContain('Rapid 2 (default)');
expect(document.body.textContent).toContain('Rapid 3 (beta)');

const buttons = Array.from(document.body.querySelectorAll('button'));
const rapid2Button = buttons
.find(button => button.textContent?.includes('Rapid 2 (default)'));
const rapid3Button = buttons
.find(button => button.textContent?.includes('Rapid 3 (beta)'));
rapid2Button?.click();
rapid3Button?.click();
await nextTick();

expect(wrapper.emitted('edit')).toContainEqual(['rapid']);
expect(wrapper.emitted('edit')).toContainEqual(['rapid3']);
});

it('keeps the primary Edit Here action on Rapid 2', async () => {
const wrapper = await mountSuspended(ReviewToolbar, {
props: { item, workspaceType: 'osw' },
global: { provide: { workspace } }
});

const editButton = wrapper.findAll('button')
.find(button => button.text().includes('Edit Here'));
await editButton?.trigger('click');

expect(wrapper.emitted('edit')).toContainEqual(['rapid']);
});
});
Loading