Browse Source

#308

feature/308
みてるぞ 10 hours ago
parent
commit
9b46e97eaf
2 changed files with 228 additions and 16 deletions
  1. +205
    -0
      backend/spec/requests/posts_spec.rb
  2. +23
    -16
      frontend/src/pages/posts/PostHistoryPage.tsx

+ 205
- 0
backend/spec/requests/posts_spec.rb View File

@@ -756,6 +756,211 @@ RSpec.describe 'Posts API', type: :request do
end end
end end


describe 'GET /posts/versions' do
let(:member) { create(:user, :member, name: 'version member') }

let(:t_v1) { Time.zone.local(2020, 1, 1, 12, 0, 0) }
let(:t_v2) { Time.zone.local(2020, 1, 2, 12, 0, 0) }
let(:t_other) { Time.zone.local(2020, 1, 3, 12, 0, 0) }

let(:oc_from) { Time.zone.local(2019, 12, 31, 0, 0, 0) }
let(:oc_before) { Time.zone.local(2020, 1, 1, 0, 0, 0) }

let!(:tag_name2) { TagName.create!(name: 'spec_tag_2') }
let!(:tag2) { Tag.create!(tag_name: tag_name2, category: :general) }

def snapshot_tags(post)
post.snapshot_tag_names.join(' ')
end

def create_post_version!(post, version_no:, event_type:, created_by_user:, created_at:)
PostVersion.create!(
post: post,
version_no: version_no,
event_type: event_type,
title: post.title,
url: post.url,
thumbnail_base: post.thumbnail_base,
tags: snapshot_tags(post),
parent: post.parent,
original_created_from: post.original_created_from,
original_created_before: post.original_created_before,
created_at: created_at,
created_by_user: created_by_user
)
end

let!(:v1) do
travel_to(t_v1) do
create_post_version!(
post_record,
version_no: 1,
event_type: 'create',
created_by_user: member,
created_at: t_v1
)
end
end

let!(:v2) do
post_record.post_tags.kept.find_by!(tag: tag).discard_by!(member)
PostTag.create!(post: post_record, tag: tag2, created_user: member)
post_record.update!(
title: 'updated spec post',
original_created_from: oc_from,
original_created_before: oc_before
)

travel_to(t_v2) do
create_post_version!(
post_record.reload,
version_no: 2,
event_type: 'update',
created_by_user: member,
created_at: t_v2
)
end
end

let!(:other_post_version) do
other_post = Post.create!(
title: 'other versioned post',
url: 'https://example.com/other-versioned'
)
PostTag.create!(post: other_post, tag: tag)

travel_to(t_other) do
create_post_version!(
other_post,
version_no: 1,
event_type: 'create',
created_by_user: member,
created_at: t_other
)
end
end

it 'returns versions for the specified post in reverse chronological order' do
get '/posts/versions', params: { post: post_record.id }

expect(response).to have_http_status(:ok)
expect(json).to include('versions', 'count')
expect(json.fetch('count')).to eq(2)

versions = json.fetch('versions')
expect(versions.map { |v| v['post_id'] }.uniq).to eq([post_record.id])
expect(versions.map { |v| v['version_no'] }).to eq([2, 1])

latest = versions.first
expect(latest).to include(
'post_id' => post_record.id,
'version_no' => 2,
'event_type' => 'update',
'created_by_user' => {
'id' => member.id,
'name' => member.name
}
)

expect(latest.fetch('title')).to eq(
'current' => 'updated spec post',
'prev' => 'spec post'
)
expect(latest.fetch('url')).to eq(
'current' => 'https://example.com/spec',
'prev' => 'https://example.com/spec'
)
expect(latest.fetch('thumbnail')).to eq(
'current' => nil,
'prev' => nil
)
expect(latest.fetch('thumbnail_base')).to eq(
'current' => nil,
'prev' => nil
)
expect(latest.fetch('tags')).to include(
{ 'name' => 'spec_tag_2', 'type' => 'added' },
{ 'name' => 'spec_tag', 'type' => 'removed' }
)
expect(latest.fetch('original_created_from')).to eq(
'current' => oc_from.iso8601,
'prev' => nil
)
expect(latest.fetch('original_created_before')).to eq(
'current' => oc_before.iso8601,
'prev' => nil
)
expect(latest.fetch('created_at')).to eq(t_v2.iso8601)

first = versions.second
expect(first).to include(
'post_id' => post_record.id,
'version_no' => 1,
'event_type' => 'create',
'created_by_user' => {
'id' => member.id,
'name' => member.name
}
)
expect(first.fetch('title')).to eq(
'current' => 'spec post',
'prev' => nil
)
expect(first.fetch('tags')).to include(
{ 'name' => 'spec_tag', 'type' => 'added' }
)
expect(first.fetch('created_at')).to eq(t_v1.iso8601)
end

it 'filters versions by tag when the current snapshot includes the tag' do
get '/posts/versions', params: { post: post_record.id, tag: tag2.id }

expect(response).to have_http_status(:ok)
expect(json.fetch('count')).to eq(1)

versions = json.fetch('versions')
expect(versions.size).to eq(1)
expect(versions[0]['post_id']).to eq(post_record.id)
expect(versions[0]['version_no']).to eq(2)
expect(versions[0]['tags']).to include(
{ 'name' => 'spec_tag_2', 'type' => 'added' }
)
end

it 'matches tag filter against current tags snapshot only' do
get '/posts/versions', params: { post: post_record.id, tag: tag.id }

expect(response).to have_http_status(:ok)
expect(json.fetch('count')).to eq(1)

versions = json.fetch('versions')
expect(versions.size).to eq(1)
expect(versions[0]['version_no']).to eq(1)
expect(versions[0]['tags']).to include(
{ 'name' => 'spec_tag', 'type' => 'added' }
)
end

it 'returns empty when tag does not exist' do
get '/posts/versions', params: { tag: 999_999_999 }

expect(response).to have_http_status(:ok)
expect(json.fetch('versions')).to eq([])
expect(json.fetch('count')).to eq(0)
end

it 'clamps page and limit to at least 1' do
get '/posts/versions', params: { post: post_record.id, page: 0, limit: 0 }

expect(response).to have_http_status(:ok)
expect(json.fetch('count')).to eq(2)

versions = json.fetch('versions')
expect(versions.size).to eq(1)
expect(versions[0]['version_no']).to eq(2)
end
end

describe 'POST /posts/:id/viewed' do describe 'POST /posts/:id/viewed' do
let(:user) { create(:user) } let(:user) { create(:user) }




+ 23
- 16
frontend/src/pages/posts/PostHistoryPage.tsx View File

@@ -215,22 +215,29 @@ export default (() => {
change.versionNo } に差戻します.\nよろしいですか?`))) change.versionNo } に差戻します.\nよろしいですか?`)))
return return


await apiPut (
`/posts/${ change.postId }`,
{ title: change.title.current,
tags: change.tags
.filter (t => t.type !== 'removed')
.map (t => t.name)
.filter (t => t.slice (0, 5) !== 'nico:')
.join (' '),
original_created_from:
change.originalCreatedFrom.current,
original_created_before:
change.originalCreatedBefore.current })

qc.invalidateQueries ({ queryKey: postsKeys.root })
qc.invalidateQueries ({ queryKey: tagsKeys.root })
toast ({ description: '更新しました.' })
try
{
await apiPut (
`/posts/${ change.postId }`,
{ title: change.title.current,
tags: change.tags
.filter (t => t.type !== 'removed')
.map (t => t.name)
.filter (t => t.slice (0, 5) !== 'nico:')
.join (' '),
original_created_from:
change.originalCreatedFrom.current,
original_created_before:
change.originalCreatedBefore.current })

qc.invalidateQueries ({ queryKey: postsKeys.root })
qc.invalidateQueries ({ queryKey: tagsKeys.root })
toast ({ description: '差戻しました.' })
}
catch
{
toast ({ description: '差戻に失敗……' })
}
}}> }}>
復元 復元
</a> </a>


Loading…
Cancel
Save