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
75 changes: 75 additions & 0 deletions e2e/features/vertex-deletion.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# language: ja

機能: 頂点削除
ユーザーがポリゴンやラインの特定の頂点を右クリックメニューやDeleteキーで削除できる

背景:
前提 アプリケーションが表示されている

シナリオ: ラインの頂点を右クリックで削除できる
前提 "ライン" モードが選択されている
もし 地図の中央をクリックする
かつ 地図の中央から右に 60px の位置をクリックする
かつ 地図の中央から右に 120px の位置をクリックする
かつ 確定ボタンをクリックする
ならば GeoJSONに 1 件のフィーチャが含まれる
かつ 1 番目のフィーチャの座標数が 3 である
もし 地図の中央から右に 60px の位置をクリックする
かつ 地図の中央から右に 60px の位置の頂点を右クリックする
ならば 頂点コンテキストメニューが表示されている
もし 「この頂点を削除」をクリックする
ならば 1 番目のフィーチャの座標数が 2 である

シナリオ: ラインの最小頂点数(2)では削除メニューが無効
前提 "ライン" モードが選択されている
もし 地図の中央をクリックする
かつ 地図の中央から右に 60px の位置をクリックする
かつ 確定ボタンをクリックする
ならば GeoJSONに 1 件のフィーチャが含まれる
もし 地図の中央をクリックする
かつ 地図の中央の頂点を右クリックする
ならば 頂点コンテキストメニューが表示されている
かつ 「この頂点を削除」ボタンが無効である

シナリオ: ポリゴンの頂点を右クリックで削除できる
前提 "ポリゴン" モードが選択されている
もし 地図の中央をクリックする
かつ 地図の中央から右に 60px の位置をクリックする
かつ 地図の中央から右に 60px 下に 60px の位置をクリックする
かつ 地図の中央から下に 60px の位置をクリックする
かつ 確定ボタンをクリックする
ならば GeoJSONに 1 件のフィーチャが含まれる
かつ 1 番目のフィーチャのポリゴン座標リングが閉じている
もし 地図の中央から右に 30px の位置をクリックする
かつ 地図の中央から右に 60px の位置の頂点を右クリックする
ならば 頂点コンテキストメニューが表示されている
もし 「この頂点を削除」をクリックする
ならば 1 番目のフィーチャのポリゴン実頂点数が 3 である
かつ 1 番目のフィーチャのポリゴン座標リングが閉じている

シナリオ: 頂点をクリック選択してDeleteキーで削除できる
前提 "ライン" モードが選択されている
もし 地図の中央をクリックする
かつ 地図の中央から右に 60px の位置をクリックする
かつ 地図の中央から右に 120px の位置をクリックする
かつ 確定ボタンをクリックする
ならば GeoJSONに 1 件のフィーチャが含まれる
かつ 1 番目のフィーチャの座標数が 3 である
もし 地図の中央から右に 60px の位置をクリックする
かつ 地図の中央から右に 60px の位置の頂点をクリック選択する
かつ Deleteキーを押す
ならば 1 番目のフィーチャの座標数が 2 である

シナリオ: 頂点削除後にUndoで復元される
前提 "ライン" モードが選択されている
もし 地図の中央をクリックする
かつ 地図の中央から右に 60px の位置をクリックする
かつ 地図の中央から右に 120px の位置をクリックする
かつ 確定ボタンをクリックする
ならば 1 番目のフィーチャの座標数が 3 である
もし 地図の中央から右に 60px の位置をクリックする
かつ 地図の中央から右に 60px の位置の頂点を右クリックする
もし 「この頂点を削除」をクリックする
ならば 1 番目のフィーチャの座標数が 2 である
もし Undo ボタンをクリックする
ならば 1 番目のフィーチャの座標数が 3 である
88 changes: 88 additions & 0 deletions e2e/step-definitions/vertex-deletion-steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { When, Then } from '@cucumber/cucumber'
import { expect } from '@playwright/test'
import { CustomWorld } from '../support/world.js'
import { clickMapAtOffset, rightClickMapAtOffset, getGeoJSONFromPanel } from '../support/helpers/map-helpers.js'
import { VERTEX_CONTEXT_MENU, VERTEX_CONTEXT_MENU_ITEM } from '../support/helpers/selectors.js'

When(
'地図の中央から右に {int}px 下に {int}px の位置をクリックする',
async function (this: CustomWorld, px: number, py: number) {
await clickMapAtOffset(this.page, px, py)
}
)

When(
'地図の中央から右に {int}px の位置の頂点を右クリックする',
{ timeout: 15000 },
async function (this: CustomWorld, offsetX: number) {
await rightClickMapAtOffset(this.page, offsetX, 0)
}
)

When(
'地図の中央の頂点を右クリックする',
{ timeout: 15000 },
async function (this: CustomWorld) {
await rightClickMapAtOffset(this.page, 0, 0)
}
)

When(
'地図の中央から右に {int}px の位置の頂点をクリック選択する',
{ timeout: 15000 },
async function (this: CustomWorld, offsetX: number) {
await clickMapAtOffset(this.page, offsetX, 0)
}
)

When('「この頂点を削除」をクリックする', { timeout: 15000 }, async function (this: CustomWorld) {
const item = this.page.locator(VERTEX_CONTEXT_MENU_ITEM, { hasText: 'この頂点を削除' })
await item.click()
await this.page.waitForTimeout(400)
})

When('Deleteキーを押す', async function (this: CustomWorld) {
await this.page.keyboard.press('Delete')
await this.page.waitForTimeout(400)
})

Then('頂点コンテキストメニューが表示されている', async function (this: CustomWorld) {
await expect(this.page.locator(VERTEX_CONTEXT_MENU)).toBeVisible()
})

Then('「この頂点を削除」ボタンが無効である', async function (this: CustomWorld) {
const item = this.page.locator(VERTEX_CONTEXT_MENU_ITEM, { hasText: 'この頂点を削除' })
await expect(item).toBeDisabled()
})

Then('{int} 番目のフィーチャの座標数が {int} である', async function (
this: CustomWorld,
index: number,
coordCount: number,
) {
const fc = await getGeoJSONFromPanel(this.page)
const feature = fc.features[index - 1]
const geom = feature.geometry
if (geom.type === 'LineString') {
expect(geom.coordinates.length).toBe(coordCount)
} else if (geom.type === 'Polygon') {
// 閉じ点を除いた実頂点数
expect(geom.coordinates[0].length - 1).toBe(coordCount)
} else {
throw new Error(`Unexpected geometry type: ${geom.type}`)
}
})

Then('{int} 番目のフィーチャのポリゴン実頂点数が {int} である', async function (
this: CustomWorld,
index: number,
vertexCount: number,
) {
const fc = await getGeoJSONFromPanel(this.page)
const geom = fc.features[index - 1].geometry
expect(geom.type).toBe('Polygon')
if (geom.type === 'Polygon') {
// 閉じ点を除いた頂点数
expect(geom.coordinates[0].length - 1).toBe(vertexCount)
}
})
4 changes: 4 additions & 0 deletions e2e/support/helpers/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export const GEOJSON_FEATURE_ITEM_HIGHLIGHTED = '.geojson-panel__feature-item--h
export const FEATURE_CONTEXT_MENU = '.feature-context-menu'
export const FEATURE_CONTEXT_MENU_ITEM = '.feature-context-menu__item'

/** 頂点コンテキストメニュー */
export const VERTEX_CONTEXT_MENU = '.vertex-context-menu'
export const VERTEX_CONTEXT_MENU_ITEM = '.vertex-context-menu__item'

/** トースト通知 */
export const MAP_TOAST = '.map-toast'

Expand Down
2 changes: 1 addition & 1 deletion src/components/GeoJSONPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { FeaturePropertiesEditor } from './FeaturePropertiesEditor'
import { parseGeoJSONImport } from '../lib/geojson-helpers'
import { parseGeoJSONImport } from '../drawing-engine'
import './GeoJSONPanel.css'

const GEOM_TYPE_LABEL: Record<string, string> = {
Expand Down
Loading