From f874d6e4054129f49f57203d5934b778dc2dde4a Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 1 Nov 2026 19:34:52 +0900 Subject: [PATCH] #434 --- backend/app/services/counts_pivot_layout.rb | 1 + .../services/counts_pivot_security_spec.rb | 25 +++++++++++++------ frontend/server.ssr.test.mjs | 6 ++++- frontend/vitest.config.ts | 1 + 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/backend/app/services/counts_pivot_layout.rb b/backend/app/services/counts_pivot_layout.rb index e523a34..2d9e897 100644 --- a/backend/app/services/counts_pivot_layout.rb +++ b/backend/app/services/counts_pivot_layout.rb @@ -28,6 +28,7 @@ class CountsPivotLayout result = [] values.each_with_index do |tuple, index| result << { values: tuple, kind: 'data' } + # A group continuing on the next page closes only on its final page. following = values[index + 1] || next_values depths.reverse_each do |depth| prefix = tuple.first(depth) diff --git a/backend/spec/services/counts_pivot_security_spec.rb b/backend/spec/services/counts_pivot_security_spec.rb index 19d0eac..337d7a5 100644 --- a/backend/spec/services/counts_pivot_security_spec.rb +++ b/backend/spec/services/counts_pivot_security_spec.rb @@ -45,17 +45,28 @@ RSpec.describe 'Counts masked pivot contracts' do end it 'preserves monthly name subtotals and grand totals across pages' do - settings = { 'resource' => 'tags', 'rows' => 'created_at:month name_ja' } + settings = { 'resource' => 'tags', 'rows' => 'created_at:month name' } complete = result(admin, settings) - paged = result(admin, settings.merge('limit' => '1')) expect(complete[:group_count]).to eq(3) expect(grand(complete)).to eq(3) - expect(grand(paged)).to eq(3) - subtotal = paged[:pivot_totals].find do |cell| - cell[:row] == ['2023-01'] && cell[:column].empty? + (1..3).each do |page| + paged = result(admin, settings.merge('limit' => '1', 'page' => page.to_s)) + expect(grand(paged)).to eq(3) + subtotal = paged[:pivot_totals].find do |cell| + cell[:row] == ['2023-01'] && cell[:column].empty? + end + headers = paged[:row_headers].select { _1[:kind] == 'subtotal' } + if page == 3 + expect(headers.map { _1[:values] }).to eq([['2023-01']]) + expect(subtotal).not_to be_nil + expect(subtotal[:count]).to eq(3) + else + expect(headers).to eq([]) + expect(subtotal).to be_nil + end + expect(paged[:cells].sum { _1[:count] }).to eq(1) + expect(paged[:group_count]).to eq(3) end - expect(subtotal[:count]).to eq(3) - expect(paged[:cells].sum { _1[:count] }).to eq(1) end it 'preserves five tag occurrences after merging private tags and across pages' do diff --git a/frontend/server.ssr.test.mjs b/frontend/server.ssr.test.mjs index d847913..cae7986 100644 --- a/frontend/server.ssr.test.mjs +++ b/frontend/server.ssr.test.mjs @@ -348,7 +348,11 @@ test ('counts SSR renders the shell without result requests or embedded results' assert.match (result.html, /]/) assert.doesNotMatch (result.html, /counts:|columns\.changedAt|fields\.actor/) assert.match (result.head, /]*name="robots"[^>]*content="noindex"/) - assert.match (result.head, /]*>[^<]*集計[^<]*\|[^<]*<\/title>/) + const titles = { + ja: { posts: '広場集計', tags: 'タグ集計' }, + 'ain-Kana': { posts: 'ミンタㇻ ウコピㇱキ', tags: 'シロシ ウコピㇱキ' } } + assert.match (result.head, new RegExp ( + `]*>${titles[locale][resource]} \\|[^<]*<\/title>`)) assert.match (result.head, /rel="canonical"/) assert.doesNotMatch (result.html, /private-discard|hidden-title|__counts_private__/) diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 593ad38..d08c97d 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -7,6 +7,7 @@ export default mergeConfig ( viteConfig, defineConfig ({ test: { environment: 'jsdom', + testTimeout: 30_000, exclude: ['**/node_modules/**', '**/dist/**', 'server.ssr.test.mjs'], setupFiles: './src/test/setup.ts', globals: false } }))