Skip to content
Open
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 .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- front-product
- front-customer
- front-other
- front-order
- front-mypage
- front-invoice
include:
- db: pgsql
database_url: postgres://postgres:password@127.0.0.1:5432/eccube_db
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ node_modules
.phpunit
/phpunit.xml
###< symfony/phpunit-bridge ###
/.phpunit.result.cache

###> friendsofphp/php-cs-fixer ###
/.php-cs-fixer.cache
Expand Down
107 changes: 107 additions & 0 deletions e2e/tests/front-mypage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,71 @@ async function addDeliveryAddress(page: Page, addr01: string) {
await expect(page.locator('div.ec-pageHeader h1')).toContainText('お届け先一覧');
}

/**
* Helper: Return the order number of the most recently created order for the
* logged-in customer by reading the first "詳細を見る" link on the order history.
* The order detail link is /mypage/history/{order_no}.
*/
async function getLatestOrderNo(page: Page): Promise<string> {
await page.goto('/mypage/');
await page.waitForLoadState('load');
const href = await page.locator('p.ec-historyListHeader__action a').first().getAttribute('href');
const matched = href?.match(/\/mypage\/history\/(.+)$/);
expect(matched, `order detail link should contain an order_no: ${href}`).not.toBeNull();
return matched![1];
}

/**
* Helper: Set the tracking number (お問い合わせ番号) of the given order's first
* shipping via the admin order/shipping edit screen. Opens a separate admin page,
* logs in, searches for the order by its order number (so the test never depends
* on global ordering), and saves the tracking number.
*/
async function setTrackingNumberViaAdmin(page: Page, orderNo: string, trackingNumber: string) {
const adminRoute = process.env.ECCUBE_ADMIN_ROUTE || 'admin';

const adminPage = await page.context().newPage();
await adminPage.goto(`/${adminRoute}/`);
await adminPage.waitForLoadState('load');
await adminPage.locator('#login_id').fill(process.env.ADMIN_USER || 'admin');
await adminPage.locator('#password').fill(process.env.ADMIN_PASSWORD || 'password');
await adminPage.getByRole('button', { name: 'ログイン' }).click();
await adminPage.waitForLoadState('load');

// Search the order by its order number and open that specific order
await adminPage.goto(`/${adminRoute}/order`);
await adminPage.waitForLoadState('load');
await adminPage.locator('#admin_search_order_multi').fill(orderNo);
await adminPage.locator('#search_form #search_submit').click();
await adminPage.waitForLoadState('load');

// Target the row whose order number cell matches orderNo, then its order link
// (so we never follow the wrong row even if the search returns multiple).
const orderRow = adminPage.locator('table tbody tr', { hasText: orderNo });
const orderLink = orderRow.locator('a[href*="/order/"]').first();
await expect(orderLink).toBeVisible();
const orderEditHref = await orderLink.getAttribute('href');
await adminPage.goto(orderEditHref!);
await adminPage.waitForLoadState('load');

// Navigate to the shipping edit page
const shippingEditLink = adminPage.locator('a[href*="/shipping/"][href*="/edit"]');
await expect(shippingEditLink).toBeVisible();
const shippingEditHref = await shippingEditLink.getAttribute('href');
await adminPage.goto(shippingEditHref!);
await adminPage.waitForLoadState('load');
await expect(adminPage.locator('.c-pageTitle')).toContainText('出荷登録');

// Fill the tracking number and save
await adminPage.locator('#form_shippings_0_tracking_number').fill(trackingNumber);
await adminPage.locator('button.ladda-button[type="submit"]').click();
await adminPage.waitForLoadState('load');
await expect(adminPage.locator('.alert-success')).toContainText('保存しました', { timeout: 30_000 });
await expect(adminPage.locator('#form_shippings_0_tracking_number')).toHaveValue(trackingNumber);

await adminPage.close();
}

test.describe('Front Mypage (EF05)', () => {

test('EF0501-UC01-T01 Mypage 初期表示', async ({ page }) => {
Expand Down Expand Up @@ -213,6 +278,48 @@ test.describe('Front Mypage (EF05)', () => {
await expect(totalBox).toContainText('合計');
});

test('EF0503-UC01-T03 Mypage ご注文履歴詳細 お問い合わせ番号表示', async ({ page }) => {
const trackingNumber = '1234567890123';

await loginAsTestCustomer(page);

// Create an order, capture its order number, then set its tracking number via admin
await createOrder(page);
const orderNo = await getLatestOrderNo(page);
await setTrackingNumberViaAdmin(page, orderNo, trackingNumber);

// Open that specific order's detail page directly (no dependency on list ordering)
await page.goto(`/mypage/history/${orderNo}`);
await page.waitForLoadState('load');
await expect(page.locator('div.ec-pageHeader h1')).toContainText('ご注文履歴詳細');

// The tracking number label and value are shown in the delivery section
const delivery = page.locator('div.ec-orderDelivery');
await expect(delivery).toContainText('お問い合わせ番号');
await expect(delivery).toContainText(trackingNumber);

// The tracking number is displayed right after the delivery time item
const labels = delivery.locator('div.ec-definitions--soft dt');
const deliveryTimeIndex = (await labels.allTextContents()).findIndex(t => t.includes('お届け時間'));
expect(deliveryTimeIndex).toBeGreaterThanOrEqual(0);
await expect(labels.nth(deliveryTimeIndex + 1)).toContainText('お問い合わせ番号');
});

test('EF0503-UC01-T04 Mypage ご注文履歴詳細 お問い合わせ番号未設定は非表示', async ({ page }) => {
await loginAsTestCustomer(page);

// Create an order without a tracking number, then open that specific order
await createOrder(page);
const orderNo = await getLatestOrderNo(page);
await page.goto(`/mypage/history/${orderNo}`);
await page.waitForLoadState('load');
await expect(page.locator('div.ec-pageHeader h1')).toContainText('ご注文履歴詳細');

// The tracking number item is not rendered (structural check on the label dt)
const delivery = page.locator('div.ec-orderDelivery');
await expect(delivery.locator('dt', { hasText: 'お問い合わせ番号' })).toHaveCount(0);
});

test('EF0503-UC01-T02 Mypage お気に入り一覧', async ({ page }) => {
await loginAsTestCustomer(page);

Expand Down
1 change: 1 addition & 0 deletions src/Eccube/Resource/locale/messages.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ front.mypage.delivery: Delivery to
front.mypage.delivery_provider: Delivery Method
front.mypage.delivery_date: Delivery Date
front.mypage.delivery_time: Delivery Time
front.mypage.tracking_number: Tracking No.
front.mypage.current_price: [Current Price]
front.mypage.reorder: Reorder
front.mypage.payment_info: Payment Info
Expand Down
1 change: 1 addition & 0 deletions src/Eccube/Resource/locale/messages.ja.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ front.mypage.delivery: お届け先
front.mypage.delivery_provider: 配送方法
front.mypage.delivery_date: お届け日
front.mypage.delivery_time: お届け時間
front.mypage.tracking_number: お問い合わせ番号
front.mypage.current_price: 【現在価格】
front.mypage.reorder: 再注文する
front.mypage.payment_info: お支払い情報
Expand Down
6 changes: 6 additions & 0 deletions src/Eccube/Resource/template/default/Mypage/history.twig
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ file that was distributed with this source code.
<dt>{{ 'front.mypage.delivery_time'|trans }} :</dt>
<dd>{{ Shipping.shipping_delivery_time|default('common.select__unspecified'|trans) }}</dd>
</div>
{% if Shipping.tracking_number %}
<div class="ec-definitions--soft">
<dt>{{ 'front.mypage.tracking_number'|trans }} :</dt>
<dd>{{ Shipping.tracking_number }}</dd>
</div>
{% endif %}
{% endfor %}
</div>
<div class="ec-orderPayment">
Expand Down
168 changes: 168 additions & 0 deletions tests/Eccube/Tests/Web/Mypage/MypageControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
use Eccube\Entity\Customer;
use Eccube\Entity\CustomerFavoriteProduct;
use Eccube\Entity\Master\OrderStatus;
use Eccube\Entity\Order;
use Eccube\Entity\Product;
use Eccube\Entity\Shipping;
use Eccube\Tests\Fixture\Generator;
use Eccube\Tests\Web\AbstractWebTestCase;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\Translation\TranslatorInterface;

final class MypageControllerTest extends AbstractWebTestCase
{
Expand Down Expand Up @@ -158,6 +162,170 @@ public function testHistoryWithNotFound()
$this->verify();
}

/**
* 注文履歴詳細を閲覧可能なステータス (NEW) の受注を生成する.
*
* createOrder() の既定ステータスは PROCESSING (仮受注) で mypage_history が 404 になるため,
* NEW へ遷移させてから返す.
*/
private function createOrderForHistory(): Order
{
$Order = $this->createOrder($this->Customer);
$Order->setOrderStatus($this->entityManager->find(OrderStatus::class, OrderStatus::NEW));

return $Order;
}

/**
* 受注に 2 つ目の配送先を追加し, お問い合わせ番号を設定する.
*/
private function addShipping(Order $Order, ?string $trackingNumber): Shipping
{
/** @var Shipping $Base */
$Base = $Order->getShippings()->first();

$Shipping = new Shipping();
$Shipping->copyProperties($this->Customer);
$Shipping
->setOrder($Order)
->setPref($Base->getPref())
->setDelivery($Base->getDelivery())
->setShippingDeliveryName($Base->getShippingDeliveryName())
->setTrackingNumber($trackingNumber)
->setCreateDate(new \DateTime())
->setUpdateDate(new \DateTime());
$Order->addShipping($Shipping);
$this->entityManager->persist($Shipping);

return $Shipping;
}

/**
* 注文履歴詳細をログイン会員として取得する.
*/
private function requestHistory(Order $Order): Crawler
{
$this->loginTo($this->Customer);

return $this->client->request(
Request::METHOD_GET,
$this->generateUrl('mypage_history', ['order_no' => $Order->getOrderNo()])
);
}

/**
* お問い合わせ番号が入力されている場合, 注文履歴詳細に表示される.
*/
public function testHistoryWithTrackingNumber(): void
{
$Order = $this->createOrderForHistory();
$Order->getShippings()->first()->setTrackingNumber('1234567890123');
$this->entityManager->flush();

$crawler = $this->requestHistory($Order);

$this->assertTrue($this->client->getResponse()->isSuccessful());
$this->assertCount(1, $crawler->filter('dt:contains("お問い合わせ番号")'));
$this->assertStringContainsString('1234567890123', $crawler->text());
}

/**
* お問い合わせ番号が null の場合, 項目自体が表示されない.
*/
public function testHistoryWithoutTrackingNumber(): void
{
$Order = $this->createOrderForHistory();
$Order->getShippings()->first()->setTrackingNumber(null);
$this->entityManager->flush();

$crawler = $this->requestHistory($Order);

$this->assertTrue($this->client->getResponse()->isSuccessful());
$this->assertCount(0, $crawler->filter('dt:contains("お問い合わせ番号")'));
}

/**
* お問い合わせ番号が空文字列の場合, 項目自体が表示されない.
*/
public function testHistoryWithEmptyTrackingNumber(): void
{
$Order = $this->createOrderForHistory();
$Order->getShippings()->first()->setTrackingNumber('');
$this->entityManager->flush();

$crawler = $this->requestHistory($Order);

$this->assertTrue($this->client->getResponse()->isSuccessful());
$this->assertCount(0, $crawler->filter('dt:contains("お問い合わせ番号")'));
}

/**
* 複数配送先がある場合, 入力済みの配送先ごとにお問い合わせ番号が表示される.
*/
public function testHistoryWithMultipleShippings(): void
{
$Order = $this->createOrderForHistory();
$Order->getShippings()->first()->setTrackingNumber('1111111111111');
$this->addShipping($Order, '2222222222222');
$this->entityManager->flush();

$crawler = $this->requestHistory($Order);

$this->assertTrue($this->client->getResponse()->isSuccessful());
$this->assertStringContainsString('1111111111111', $crawler->text());
$this->assertStringContainsString('2222222222222', $crawler->text());
// 配送先ごとに「お問い合わせ番号」ラベルが出力される (2 件).
$this->assertCount(2, $crawler->filter('dt:contains("お問い合わせ番号")'));
}

/**
* 複数配送先のうち一部のみ入力されている場合, 入力済みの配送先のみ表示される.
*/
public function testHistoryWithMultipleShippingsPartiallyFilled(): void
{
$Order = $this->createOrderForHistory();
$Order->getShippings()->first()->setTrackingNumber('1111111111111');
// 2 つ目の配送先はお問い合わせ番号未入力.
$this->addShipping($Order, null);
$this->entityManager->flush();

$crawler = $this->requestHistory($Order);

$this->assertTrue($this->client->getResponse()->isSuccessful());
$this->assertStringContainsString('1111111111111', $crawler->text());
// 入力済みは 1 件のみ.
$this->assertCount(1, $crawler->filter('dt:contains("お問い合わせ番号")'));
}

/**
* お問い合わせ番号に HTML 特殊文字が含まれていても自動エスケープされ, スクリプトが実行されない.
*/
public function testHistoryTrackingNumberXss(): void
{
$Order = $this->createOrderForHistory();
$Order->getShippings()->first()->setTrackingNumber('<script>alert("XSS")</script>');
$this->entityManager->flush();

$this->requestHistory($Order);

$this->assertTrue($this->client->getResponse()->isSuccessful());
$html = $this->client->getResponse()->getContent();
$this->assertStringContainsString('&lt;script&gt;', (string) $html);
$this->assertStringNotContainsString('<script>alert', (string) $html);
}

/**
* お問い合わせ番号ラベルの翻訳定義 (日本語 / 英語).
*/
public function testTrackingNumberLabelTranslations(): void
{
/** @var TranslatorInterface $translator */
$translator = static::getContainer()->get(TranslatorInterface::class);

$this->assertSame('お問い合わせ番号', $translator->trans('front.mypage.tracking_number', [], null, 'ja'));
$this->assertSame('Tracking No.', $translator->trans('front.mypage.tracking_number', [], null, 'en'));
}

/**
* Paginator を経由したお気に入りの取得
*
Expand Down
Loading