split my_page module off of grids

This commit is contained in:
Jens Ulferts
2019-04-05 08:46:10 +02:00
parent 02e26879ac
commit aaa7216d80
42 changed files with 1383 additions and 952 deletions
+7
View File
@@ -180,6 +180,12 @@ PATH
openproject-meeting (1.0.0)
icalendar (~> 2.5.0)
PATH
remote: modules/my_page
specs:
my_page (1.0.0)
grids
PATH
remote: modules/my_project_page
specs:
@@ -970,6 +976,7 @@ DEPENDENCIES
lograge (~> 0.10.0)
meta-tags (~> 2.11.0)
multi_json (~> 1.13.1)
my_page!
mysql2 (~> 0.5.0)
net-ldap (~> 0.16.0)
newrelic_rpm
+1
View File
@@ -40,6 +40,7 @@ group :opf_plugins do
gem 'openproject-ldap_groups', path: 'modules/ldap_groups'
gem 'grids', path: 'modules/grids'
gem 'my_page', path: 'modules/my_page'
gem 'openproject-boards', path: 'modules/boards'
gem 'openproject-bim_seeder', path: 'modules/bim_seeder', require: !!(ENV['OPENPROJECT_EDITION'] == 'bim')
@@ -76,8 +76,7 @@ module API
value_representer: false,
link_factory: ->(path) {
{
href: path,
title: I18n.t(:label_my_page)
href: path
}
}
-14
View File
@@ -1,14 +0,0 @@
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails gems
# installed from the root of your application.
ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/grids/engine', __FILE__)
APP_PATH = File.expand_path('../../test/dummy/config/application', __FILE__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
require 'rails/all'
require 'rails/engine/commands'
-4
View File
@@ -9,9 +9,5 @@ module Grids
Queries::Register.filter query, Grids::Filters::ScopeFilter
end
config.to_prepare do
Grids::MyPageGridRegistration.register!
end
end
end
@@ -38,9 +38,11 @@ describe Grids::CreateContract do
it_behaves_like 'shared grid contract attributes'
describe 'type' do
let(:grid) { FactoryBot.build_stubbed(:grid, default_values) }
it_behaves_like 'is writable' do
let(:attribute) { :type }
let(:value) { 'Grids::MyPage' }
let(:value) { 'Grids::Grid' }
end
end
@@ -51,15 +53,6 @@ describe Grids::CreateContract do
let(:attribute) { :user_id }
let(:value) { 5 }
end
context 'for a Grids::MyPage' do
let(:grid) { FactoryBot.build_stubbed(:my_page, default_values) }
it_behaves_like 'is writable' do
let(:attribute) { :user_id }
let(:value) { 5 }
end
end
end
describe 'project_id' do
@@ -69,15 +62,6 @@ describe Grids::CreateContract do
let(:attribute) { :project_id }
let(:value) { 5 }
end
context 'for a Grids::MyPage' do
let(:grid) { FactoryBot.build_stubbed(:my_page, default_values) }
it_behaves_like 'is not writable' do
let(:attribute) { :project_id }
let(:value) { 5 }
end
end
end
describe '#assignable_values' do
@@ -104,12 +88,12 @@ describe Grids::CreateContract do
allow(Grids::Configuration)
.to receive(:allowed_widget?)
.with(Grids::MyPage, :widget1, user)
.with(Grids::Grid, :widget1, user)
.and_return(true)
allow(Grids::Configuration)
.to receive(:allowed_widget?)
.with(Grids::MyPage, :widget2, user)
.with(Grids::Grid, :widget2, user)
.and_return(false)
expect(instance.assignable_values(:widgets, user))
@@ -39,7 +39,7 @@ shared_context 'grid contract' do
}
end
let(:grid) do
FactoryBot.build_stubbed(:my_page, default_values)
FactoryBot.build_stubbed(:grid, default_values)
end
shared_examples_for 'validates positive integer' do
@@ -113,261 +113,7 @@ shared_examples_for 'shared grid contract attributes' do
end
end
describe 'widgets' do
it_behaves_like 'is writable' do
let(:attribute) { :widgets }
let(:value) do
[
Grids::Widget.new(start_row: 1,
end_row: 4,
start_column: 2,
end_column: 5,
identifier: 'work_packages_assigned')
]
end
end
context 'invalid identifier' do
before do
grid.widgets.build(start_row: 1,
end_row: 4,
start_column: 2,
end_column: 5,
identifier: 'bogus_identifier')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :inclusion }]
end
end
context 'collisions between widgets' do
before do
grid.widgets.build(start_row: 1,
end_row: 3,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
grid.widgets.build(start_row: 2,
end_row: 4,
start_column: 2,
end_column: 4,
identifier: 'work_packages_created')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :overlaps }, { error: :overlaps }]
end
end
context 'widgets having the same start column as another\'s end column' do
before do
grid.widgets.build(start_row: 1,
end_row: 3,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
grid.widgets.build(start_row: 1,
end_row: 3,
start_column: 3,
end_column: 4,
identifier: 'work_packages_created')
end
it 'is valid' do
expect(instance.validate)
.to be_truthy
end
end
context 'widgets having the same start row as another\'s end row' do
before do
grid.widgets.build(start_row: 1,
end_row: 3,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
grid.widgets.build(start_row: 3,
end_row: 4,
start_column: 1,
end_column: 3,
identifier: 'work_packages_created')
end
it 'is valid' do
expect(instance.validate)
.to be_truthy
end
end
context 'widgets being outside (max) of the grid' do
before do
grid.widgets.build(start_row: 1,
end_row: grid.row_count + 2,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :outside }]
end
end
context 'widgets being outside (min) of the grid' do
before do
grid.widgets.build(start_row: 1,
end_row: 2,
start_column: -1,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :outside }]
end
end
context 'widgets spanning the whole grid' do
before do
grid.widgets.build(start_row: 1,
end_row: grid.row_count + 1,
start_column: 1,
end_column: grid.column_count + 1,
identifier: 'work_packages_assigned')
end
it 'is valid' do
expect(instance.validate)
.to be_truthy
end
end
context 'widgets having start after end column' do
before do
grid.widgets.build(start_row: 1,
end_row: 2,
start_column: 4,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :end_before_start }]
end
end
context 'widgets having start after end row' do
before do
grid.widgets.build(start_row: 4,
end_row: 2,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :end_before_start }]
end
end
context 'widgets having start equals end column' do
before do
grid.widgets.build(start_row: 1,
end_row: 2,
start_column: 4,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :end_before_start }]
end
end
context 'widgets having start equals end row' do
before do
grid.widgets.build(start_row: 2,
end_row: 2,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :end_before_start }]
end
end
end
describe 'valid grid subclasses' do
context 'for a registered subclass' do
let(:grid) do
FactoryBot.build_stubbed(:my_page, default_values)
end
it 'is valid' do
expect(instance.validate)
.to be_truthy
end
end
context 'for the Grid superclass itself' do
let(:grid) do
FactoryBot.build_stubbed(:grid, default_values)
@@ -51,7 +51,7 @@ describe Grids::UpdateContract do
instance.validate
# scope because that is what type is called on the outside for grids
expect(instance.errors.details[:scope])
.to match_array [{ error: :error_readonly }]
.to match_array [{ error: :error_readonly }, { error: :inclusion }]
end
end
@@ -1,28 +1,4 @@
FactoryBot.define do
factory :grid, class: Grids::Grid do
end
factory :my_page, class: Grids::MyPage do
user
row_count { 7 }
column_count { 4 }
widgets do
[
Grids::Widget.new(
identifier: 'work_packages_assigned',
start_row: 1,
end_row: 7,
start_column: 1,
end_column: 3
),
Grids::Widget.new(
identifier: 'work_packages_created',
start_row: 1,
end_row: 7,
start_column: 3,
end_column: 5
)
]
end
end
end
@@ -71,7 +71,7 @@ describe ::API::V3::Grids::GridPayloadRepresenter, 'parsing' do
],
"_links" => {
"scope" => {
"href" => my_page_path
"href" => 'some_path'
}
}
}
@@ -82,7 +82,7 @@ describe ::API::V3::Grids::GridPayloadRepresenter, 'parsing' do
it 'updates page' do
grid = representer.from_hash(hash)
expect(grid.scope)
.to eql(my_page_path)
.to eql('some_path')
end
end
end
@@ -33,7 +33,7 @@ describe ::API::V3::Grids::GridRepresenter, 'rendering' do
let(:grid) do
FactoryBot.build_stubbed(
:my_page,
:grid,
row_count: 4,
column_count: 5,
widgets: [
@@ -68,6 +68,21 @@ describe ::API::V3::Grids::GridRepresenter, 'rendering' do
let(:current_user) { FactoryBot.build_stubbed(:user) }
let(:representer) { described_class.new(grid, current_user: current_user) }
let(:writable) { true }
let(:scope_path) { 'bogus_scope' }
before do
allow(::Grids::Configuration)
.to receive(:writable?)
.with(grid, current_user)
.and_return(writable)
allow(::Grids::Configuration)
.to receive(:to_scope)
.with(Grids::Grid, [])
.and_return(scope_path)
end
context 'generation' do
subject(:generated) { representer.to_json }
@@ -78,12 +93,6 @@ describe ::API::V3::Grids::GridRepresenter, 'rendering' do
.at_path('_type')
end
it 'identifies the url the grid is stored for' do
is_expected
.to be_json_eql(my_page_path.to_json)
.at_path('_links/scope/href')
end
it 'has an id' do
is_expected
.to be_json_eql(grid.id)
@@ -180,7 +189,7 @@ describe ::API::V3::Grids::GridRepresenter, 'rendering' do
context 'scope link' do
it_behaves_like 'has an untitled link' do
let(:link) { 'scope' }
let(:href) { my_page_path }
let(:href) { scope_path }
let(:type) { "text/html" }
it 'has a content type of html' do
@@ -61,16 +61,6 @@ describe "POST /api/v3/grids/form", type: :request, content_type: :json do
.at_path('_type')
end
it 'contains a Schema embedding the available values' do
expect(subject.body)
.to be_json_eql("Schema".to_json)
.at_path('_embedded/schema/_type')
expect(subject.body)
.to be_json_eql(my_page_path.to_json)
.at_path('_embedded/schema/scope/_links/allowedValues/0/href')
end
it 'contains default data in the payload' do
expected = {
"rowCount": 4,
@@ -95,135 +85,5 @@ describe "POST /api/v3/grids/form", type: :request, content_type: :json do
expect(subject.body)
.not_to have_json_path('_links/commit')
end
context 'with /my/page for the scope value' do
let(:params) do
{
'_links': {
'scope': {
'href': my_page_path
}
}
}
end
it 'contains default data in the payload' do
expected = {
"rowCount": 7,
"columnCount": 4,
"options": {},
"widgets": [
{
"_type": "GridWidget",
identifier: 'work_packages_assigned',
"options": {},
startRow: 1,
endRow: 7,
startColumn: 1,
endColumn: 3
},
{
"_type": "GridWidget",
identifier: 'work_packages_created',
"options": {},
startRow: 1,
endRow: 7,
startColumn: 3,
endColumn: 5
}
],
"_links": {
"scope": {
"href": "/my/page",
"type": "text/html"
}
}
}
expect(subject.body)
.to be_json_eql(expected.to_json)
.at_path('_embedded/payload')
end
it 'has no validationErrors' do
expect(subject.body)
.to be_json_eql({}.to_json)
.at_path('_embedded/validationErrors')
end
it 'has a commit link' do
expect(subject.body)
.to be_json_eql(api_v3_paths.grids.to_json)
.at_path('_links/commit/href')
end
end
context 'with an unsupported widget identifier' do
let(:params) do
{
'_links': {
'scope': {
'href': my_page_path
}
},
"widgets": [
{
"_type": "GridWidget",
"identifier": "bogus_identifier",
"startRow": 4,
"endRow": 5,
"startColumn": 1,
"endColumn": 2
}
]
}
end
it 'has a validationError on widget' do
expect(subject.body)
.to be_json_eql("Widgets is not set to one of the allowed values.".to_json)
.at_path('_embedded/validationErrors/widgets/message')
end
end
context 'with name set' do
let(:params) do
{
name: 'My custom grid 1',
'_links': {
'scope': {
'href': my_page_path
}
}
}
end
it 'feeds it back' do
expect(subject.body)
.to be_json_eql("My custom grid 1".to_json)
.at_path('_embedded/payload/name')
end
end
context 'with options set' do
let(:params) do
{
options: {
foo: 'bar'
},
'_links': {
'scope': {
'href': my_page_path
}
}
}
end
it 'feeds them back' do
expect(subject.body)
.to be_json_eql("bar".to_json)
.at_path('_embedded/payload/options/foo')
end
end
end
end
@@ -37,12 +37,6 @@ describe 'API v3 Grids resource', type: :request, content_type: :json do
FactoryBot.create(:user)
end
let(:my_page_grid) { FactoryBot.create(:my_page, user: current_user) }
let(:other_user) do
FactoryBot.create(:user)
end
let(:other_my_page_grid) { FactoryBot.create(:my_page, user: other_user) }
before do
login_as(current_user)
end
@@ -52,369 +46,22 @@ describe 'API v3 Grids resource', type: :request, content_type: :json do
describe '#get INDEX' do
let(:path) { api_v3_paths.grids }
let(:stored_grids) do
my_page_grid
other_my_page_grid
end
before do
stored_grids
get path
end
it 'responds with 200 OK' do
expect(subject.status).to eq(200)
end
it 'sends a collection of grids but only those visible to the current user' do
expect(subject.body)
.to be_json_eql('Collection'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_embedded/elements/0/_type')
expect(subject.body)
.to be_json_eql(1.to_json)
.at_path('total')
end
context 'with a filter on the scope attribute' do
shared_let(:other_grid) do
grid = Grids::Grid.new(row_count: 20,
column_count: 20)
grid.save
Grids::Grid
.where(id: grid.id)
.update_all(user_id: current_user.id)
grid
end
let(:stored_grids) do
my_page_grid
other_my_page_grid
other_grid
end
let(:path) do
filter = [{ 'scope' =>
{
'operator' => '=',
'values' => [my_page_path]
} }]
"#{api_v3_paths.grids}?#{{ filters: filter.to_json }.to_query}"
end
it 'responds with 200 OK' do
expect(subject.status).to eq(200)
end
it 'sends only the my page of the current user' do
expect(subject.body)
.to be_json_eql('Collection'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_embedded/elements/0/_type')
expect(subject.body)
.to be_json_eql(1.to_json)
.at_path('total')
end
end
end
describe '#get' do
let(:path) { api_v3_paths.grid(my_page_grid.id) }
let(:stored_grids) do
my_page_grid
end
before do
stored_grids
get path
end
it 'responds with 200 OK' do
expect(subject.status).to eq(200)
end
it 'sends a grid block' do
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_type')
end
it 'identifies the url the grid is stored for' do
expect(subject.body)
.to be_json_eql(my_page_path.to_json)
.at_path('_links/scope/href')
end
context 'with the page not existing' do
let(:path) { api_v3_paths.grid(5) }
it 'responds with 404 NOT FOUND' do
expect(subject.status).to eql 404
end
end
context 'with the grid belonging to someone else' do
let(:stored_grids) do
my_page_grid
other_my_page_grid
end
let(:path) { api_v3_paths.grid(other_my_page_grid.id) }
it 'responds with 404 NOT FOUND' do
expect(subject.status).to eql 404
end
end
end
describe '#patch' do
let(:path) { api_v3_paths.grid(my_page_grid.id) }
let(:params) do
{
"rowCount": 10,
"name": 'foo',
"columnCount": 15,
"widgets": [{
"identifier": "work_packages_assigned",
"startRow": 4,
"endRow": 8,
"startColumn": 2,
"endColumn": 5
}]
}.with_indifferent_access
end
let(:stored_grids) do
my_page_grid
end
before do
stored_grids
patch path, params.to_json, 'CONTENT_TYPE' => 'application/json'
end
it 'responds with 200 OK' do
expect(subject.status).to eq(200)
end
it 'returns the altered grid block' do
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql('foo'.to_json)
.at_path('name')
expect(subject.body)
.to be_json_eql(params['rowCount'].to_json)
.at_path('rowCount')
expect(subject.body)
.to be_json_eql(params['widgets'][0]['identifier'].to_json)
.at_path('widgets/0/identifier')
end
it 'perists the changes' do
expect(my_page_grid.reload.row_count)
.to eql params['rowCount']
end
context 'with invalid params' do
let(:params) do
{
"rowCount": -5,
"columnCount": 15,
"widgets": [{
"identifier": "work_packages_assigned",
"startRow": 4,
"endRow": 8,
"startColumn": 2,
"endColumn": 5
}]
}.with_indifferent_access
end
it 'responds with 422 and mentions the error' do
expect(subject.status).to eq 422
expect(subject.body)
.to be_json_eql('Error'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql("Widgets is outside of the grid.".to_json)
.at_path('_embedded/errors/0/message')
expect(subject.body)
.to be_json_eql("Number of rows must be greater than 0.".to_json)
.at_path('_embedded/errors/1/message')
end
it 'does not persist the changes to widgets' do
expect(my_page_grid.reload.widgets.count)
.to eql Grids::MyPageGridRegistration.defaults[:widgets].size
end
end
context 'with a scope param' do
let(:params) do
{
"_links": {
"scope": {
"href": ''
}
}
}.with_indifferent_access
end
it 'responds with 422 and mentions the error' do
expect(subject.status).to eq 422
expect(subject.body)
.to be_json_eql('Error'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql("You must not write a read-only attribute.".to_json)
.at_path('message')
expect(subject.body)
.to be_json_eql("scope".to_json)
.at_path('_embedded/details/attribute')
end
end
context 'with the page not existing' do
let(:path) { api_v3_paths.grid(5) }
it 'responds with 404 NOT FOUND' do
expect(subject.status).to eql 404
end
end
context 'with the grid belonging to someone else' do
let(:stored_grids) do
my_page_grid
other_my_page_grid
end
let(:path) { api_v3_paths.grid(other_my_page_grid.id) }
it 'responds with 404 NOT FOUND' do
expect(subject.status).to eql 404
end
end
end
describe '#post' do
let(:path) { api_v3_paths.grids }
let(:params) do
{
"rowCount": 10,
"columnCount": 15,
"widgets": [{
"identifier": "work_packages_assigned",
"startRow": 4,
"endRow": 8,
"startColumn": 2,
"endColumn": 5
}],
"_links": {
"scope": {
"href": my_page_path
}
}
}.with_indifferent_access
end
before do
post path, params.to_json, 'CONTENT_TYPE' => 'application/json'
end
it 'responds with 201 CREATED' do
expect(subject.status).to eq(201)
end
it 'returns the created grid block' do
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql(params['rowCount'].to_json)
.at_path('rowCount')
expect(subject.body)
.to be_json_eql(params['widgets'][0]['identifier'].to_json)
.at_path('widgets/0/identifier')
end
it 'persists the grid' do
expect(Grids::Grid.count)
.to eql(1)
end
context 'with invalid params' do
let(:params) do
{
"rowCount": -5,
"columnCount": "sdjfksdfsdfdsf",
"widgets": [{
"identifier": "work_packages_assigned",
"startRow": 4,
"endRow": 8,
"startColumn": 2,
"endColumn": 5
}],
"_links": {
"scope": {
"href": my_page_path
}
}
}.with_indifferent_access
end
it 'responds with 422' do
expect(subject.status).to eq(422)
end
it 'does not create a grid' do
expect(Grids::Grid.count)
.to eql(0)
end
it 'returns the errors' do
expect(subject.body)
.to be_json_eql('Error'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql("Widgets is outside of the grid.".to_json)
.at_path('_embedded/errors/0/message')
expect(subject.body)
.to be_json_eql("Number of rows must be greater than 0.".to_json)
.at_path('_embedded/errors/1/message')
expect(subject.body)
.to be_json_eql("Number of columns must be greater than 0.".to_json)
.at_path('_embedded/errors/2/message')
end
end
context 'without a page link' do
let(:params) do
{
@@ -37,10 +37,6 @@ describe "PATCH /api/v3/grids/:id/form", type: :request, content_type: :json do
FactoryBot.create(:user)
end
let(:grid) do
FactoryBot.create(:my_page, user: current_user)
end
let(:path) { api_v3_paths.grid_form(grid.id) }
let(:params) { {} }
subject(:response) { last_response }
@@ -53,126 +49,8 @@ describe "PATCH /api/v3/grids/:id/form", type: :request, content_type: :json do
post path, params.to_json, 'CONTENT_TYPE' => 'application/json'
end
it 'returns 200 OK' do
expect(subject.status)
.to eql 200
end
it 'is of type form' do
expect(subject.body)
.to be_json_eql("Form".to_json)
.at_path('_type')
end
it 'contains a Schema disallowing setting scope' do
expect(subject.body)
.to be_json_eql("Schema".to_json)
.at_path('_embedded/schema/_type')
expect(subject.body)
.to be_json_eql(false.to_json)
.at_path('_embedded/schema/scope/writable')
end
it 'contains the current data in the payload' do
expected = {
rowCount: 7,
columnCount: 4,
options: {},
widgets: [
{
"_type": "GridWidget",
identifier: 'work_packages_assigned',
options: {},
startRow: 1,
endRow: 7,
startColumn: 1,
endColumn: 3
},
{
"_type": "GridWidget",
identifier: 'work_packages_created',
options: {},
startRow: 1,
endRow: 7,
startColumn: 3,
endColumn: 5
}
],
"_links": {
"scope": {
"href": "/my/page",
"type": "text/html"
}
}
}
expect(subject.body)
.to be_json_eql(expected.to_json)
.at_path('_embedded/payload')
end
it 'has a commit link' do
expect(subject.body)
.to be_json_eql(api_v3_paths.grid(grid.id).to_json)
.at_path('_links/commit/href')
end
context 'with some value for the scope value' do
let(:params) do
{
'_links': {
'scope': {
'href': '/some/path'
}
}
}
end
it 'has a validation error on scope as the value is not writeable' do
expect(subject.body)
.to be_json_eql("You must not write a read-only attribute.".to_json)
.at_path('_embedded/validationErrors/scope/message')
end
end
context 'with an unsupported widget identifier' do
let(:params) do
{
"widgets": [
{
"_type": "GridWidget",
"identifier": "bogus_identifier",
"startRow": 4,
"endRow": 5,
"startColumn": 1,
"endColumn": 2
}
]
}
end
it 'has a validationError on widget' do
expect(subject.body)
.to be_json_eql("Widgets is not set to one of the allowed values.".to_json)
.at_path('_embedded/validationErrors/widgets/message')
end
end
context 'for a non existing grid' do
let(:path) { api_v3_paths.grid_form(grid.id + 5) }
it 'returns 404 NOT FOUND' do
expect(subject.status)
.to eql 404
end
end
context 'for another user\'s grid' do
let(:other_user) { FactoryBot.create(:user) }
let(:other_grid) { FactoryBot.create(:my_page, user: other_user) }
let(:path) { api_v3_paths.grid_form(other_grid.id) }
let(:path) { api_v3_paths.grid_form(5) }
it 'returns 404 NOT FOUND' do
expect(subject.status)
@@ -50,7 +50,9 @@ describe Grids::CreateService, type: :model do
end
let(:scope) { "some/scope/url" }
let(:call_attributes) { { scope: scope } }
let(:grid_class) { Grids::MyPage }
let(:grid_class) do
Grids::Grid
end
let(:set_attributes_success) do
true
end
@@ -56,7 +56,7 @@ describe Grids::SetAttributesService, type: :model do
contract_class: contract_class)
end
let(:call_attributes) { {} }
let(:grid_class) { Grids::MyPage }
let(:grid_class) { Grids::Grid }
let(:grid) do
FactoryBot.build_stubbed(grid_class.name.demodulize.underscore.to_sym, widgets: [])
end
@@ -42,7 +42,7 @@ describe Grids::UpdateService, type: :model do
contract_class: contract_class)
end
let(:call_attributes) { {} }
let(:grid_class) { Grids::MyPage }
let(:grid_class) { Grids::Grid }
let(:set_attributes_success) do
true
end
+7
View File
@@ -0,0 +1,7 @@
.bundle/
log/*.log
pkg/
test/dummy/db/*.sqlite3
test/dummy/db/*.sqlite3-journal
test/dummy/log/*.log
test/dummy/tmp/
+3
View File
@@ -0,0 +1,3 @@
source 'https://rubygems.org'
gemspec
+4
View File
@@ -0,0 +1,4 @@
require "my_page/engine"
module MyPage
end
+11
View File
@@ -0,0 +1,11 @@
module MyPage
class Engine < ::Rails::Engine
isolate_namespace MyPage
include OpenProject::Plugins::ActsAsOpEngine
config.to_prepare do
MyPage::GridRegistration.register!
end
end
end
@@ -1,5 +1,5 @@
module Grids
class MyPageGridRegistration < ::Grids::Configuration::Registration
module MyPage
class GridRegistration < ::Grids::Configuration::Registration
grid_class 'Grids::MyPage'
to_scope :my_page_path
+12
View File
@@ -0,0 +1,12 @@
# encoding: UTF-8
Gem::Specification.new do |s|
s.name = "my_page"
s.version = '1.0.0'
s.authors = ["OpenProject"]
s.summary = "OpenProject MyPage."
s.files = Dir["{app,config,db,lib}/**/*"]
s.add_dependency 'grids'
end
@@ -0,0 +1,61 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require_relative './shared_examples'
describe Grids::CreateContract do
include_context 'grid contract'
include_context 'model contract'
it_behaves_like 'shared grid contract attributes'
describe 'user_id' do
context 'for a Grids::MyPage' do
let(:grid) { FactoryBot.build_stubbed(:my_page, default_values) }
it_behaves_like 'is writable' do
let(:attribute) { :user_id }
let(:value) { 5 }
end
end
end
describe 'project_id' do
context 'for a Grids::MyPage' do
let(:grid) { FactoryBot.build_stubbed(:my_page, default_values) }
it_behaves_like 'is not writable' do
let(:attribute) { :project_id }
let(:value) { 5 }
end
end
end
end
@@ -0,0 +1,320 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
shared_context 'grid contract' do
let(:user) { FactoryBot.build_stubbed(:user) }
let(:instance) { described_class.new(grid, user) }
let(:default_values) do
{
row_count: 6,
column_count: 7,
widgets: []
}
end
let(:grid) do
FactoryBot.build_stubbed(:my_page, default_values)
end
end
shared_examples_for 'shared grid contract attributes' do
include_context 'model contract'
let(:model) { grid }
describe 'widgets' do
it_behaves_like 'is writable' do
let(:attribute) { :widgets }
let(:value) do
[
Grids::Widget.new(start_row: 1,
end_row: 4,
start_column: 2,
end_column: 5,
identifier: 'work_packages_assigned')
]
end
end
context 'invalid identifier' do
before do
grid.widgets.build(start_row: 1,
end_row: 4,
start_column: 2,
end_column: 5,
identifier: 'bogus_identifier')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :inclusion }]
end
end
context 'collisions between widgets' do
before do
grid.widgets.build(start_row: 1,
end_row: 3,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
grid.widgets.build(start_row: 2,
end_row: 4,
start_column: 2,
end_column: 4,
identifier: 'work_packages_created')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :overlaps }, { error: :overlaps }]
end
end
context 'widgets having the same start column as another\'s end column' do
before do
grid.widgets.build(start_row: 1,
end_row: 3,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
grid.widgets.build(start_row: 1,
end_row: 3,
start_column: 3,
end_column: 4,
identifier: 'work_packages_created')
end
it 'is valid' do
expect(instance.validate)
.to be_truthy
end
end
context 'widgets having the same start row as another\'s end row' do
before do
grid.widgets.build(start_row: 1,
end_row: 3,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
grid.widgets.build(start_row: 3,
end_row: 4,
start_column: 1,
end_column: 3,
identifier: 'work_packages_created')
end
it 'is valid' do
expect(instance.validate)
.to be_truthy
end
end
context 'widgets being outside (max) of the grid' do
before do
grid.widgets.build(start_row: 1,
end_row: grid.row_count + 2,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :outside }]
end
end
context 'widgets being outside (min) of the grid' do
before do
grid.widgets.build(start_row: 1,
end_row: 2,
start_column: -1,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :outside }]
end
end
context 'widgets spanning the whole grid' do
before do
grid.widgets.build(start_row: 1,
end_row: grid.row_count + 1,
start_column: 1,
end_column: grid.column_count + 1,
identifier: 'work_packages_assigned')
end
it 'is valid' do
expect(instance.validate)
.to be_truthy
end
end
context 'widgets having start after end column' do
before do
grid.widgets.build(start_row: 1,
end_row: 2,
start_column: 4,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :end_before_start }]
end
end
context 'widgets having start after end row' do
before do
grid.widgets.build(start_row: 4,
end_row: 2,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :end_before_start }]
end
end
context 'widgets having start equals end column' do
before do
grid.widgets.build(start_row: 1,
end_row: 2,
start_column: 4,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :end_before_start }]
end
end
context 'widgets having start equals end row' do
before do
grid.widgets.build(start_row: 2,
end_row: 2,
start_column: 1,
end_column: 3,
identifier: 'work_packages_assigned')
end
it 'is invalid' do
expect(instance.validate)
.to be_falsey
end
it 'notes the error' do
instance.validate
expect(instance.errors.details[:widgets])
.to match_array [{ error: :end_before_start }]
end
end
end
describe 'valid grid subclasses' do
context 'for a registered subclass' do
let(:grid) do
FactoryBot.build_stubbed(:my_page, default_values)
end
it 'is valid' do
expect(instance.validate)
.to be_truthy
end
end
context 'for the Grid superclass itself' do
let(:grid) do
FactoryBot.build_stubbed(:grid, default_values)
end
before do
instance.validate
end
it 'is invalid for the grid superclass itself' do
expect(instance.errors.details[:scope])
.to match_array [{ error: :inclusion }]
end
end
end
end
@@ -0,0 +1,39 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require_relative './shared_examples'
describe Grids::UpdateContract do
include_context 'model contract'
include_context 'grid contract'
it_behaves_like 'shared grid contract attributes'
end
@@ -0,0 +1,25 @@
FactoryBot.define do
factory :my_page, class: Grids::MyPage do
user
row_count { 7 }
column_count { 4 }
widgets do
[
Grids::Widget.new(
identifier: 'work_packages_assigned',
start_row: 1,
end_row: 7,
start_column: 1,
end_column: 3
),
Grids::Widget.new(
identifier: 'work_packages_created',
start_row: 1,
end_row: 7,
start_column: 3,
end_column: 5
)
]
end
end
end
@@ -0,0 +1,77 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
shared_examples_for 'grid attributes' do
describe 'attributes' do
it '#row_count' do
instance.row_count = 5
expect(instance.row_count)
.to eql 5
end
it '#column_count' do
instance.column_count = 5
expect(instance.column_count)
.to eql 5
end
it '#name' do
instance.name = 'custom 123'
expect(instance.name)
.to eql 'custom 123'
# can be empty
instance.name = nil
expect(instance).to be_valid
end
it '#options' do
value = {
some: 'value',
and: {
also: 1
}
}
instance.options = value
expect(instance.options)
.to eql value
end
it '#widgets' do
widgets = [
Grids::Widget.new(start_row: 2),
Grids::Widget.new(start_row: 5)
]
instance.widgets = widgets
expect(instance.widgets)
.to match_array widgets
end
end
end
@@ -0,0 +1,193 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe "POST /api/v3/grids/form", type: :request, content_type: :json do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
shared_let(:current_user) do
FactoryBot.create(:user)
end
let(:path) { api_v3_paths.create_grid_form }
let(:params) { {} }
subject(:response) { last_response }
before do
login_as(current_user)
end
describe '#post' do
before do
post path, params.to_json, 'CONTENT_TYPE' => 'application/json'
end
it 'contains a Schema embedding the available values' do
expect(subject.body)
.to be_json_eql("Schema".to_json)
.at_path('_embedded/schema/_type')
expect(subject.body)
.to be_json_eql(my_page_path.to_json)
.at_path('_embedded/schema/scope/_links/allowedValues/0/href')
end
context 'with /my/page for the scope value' do
let(:params) do
{
'_links': {
'scope': {
'href': my_page_path
}
}
}
end
it 'contains default data in the payload' do
expected = {
"rowCount": 7,
"columnCount": 4,
"options": {},
"widgets": [
{
"_type": "GridWidget",
identifier: 'work_packages_assigned',
"options": {},
startRow: 1,
endRow: 7,
startColumn: 1,
endColumn: 3
},
{
"_type": "GridWidget",
identifier: 'work_packages_created',
"options": {},
startRow: 1,
endRow: 7,
startColumn: 3,
endColumn: 5
}
],
"_links": {
"scope": {
"href": "/my/page",
"type": "text/html"
}
}
}
expect(subject.body)
.to be_json_eql(expected.to_json)
.at_path('_embedded/payload')
end
it 'has no validationErrors' do
expect(subject.body)
.to be_json_eql({}.to_json)
.at_path('_embedded/validationErrors')
end
it 'has a commit link' do
expect(subject.body)
.to be_json_eql(api_v3_paths.grids.to_json)
.at_path('_links/commit/href')
end
end
context 'with an unsupported widget identifier' do
let(:params) do
{
'_links': {
'scope': {
'href': my_page_path
}
},
"widgets": [
{
"_type": "GridWidget",
"identifier": "bogus_identifier",
"startRow": 4,
"endRow": 5,
"startColumn": 1,
"endColumn": 2
}
]
}
end
it 'has a validationError on widget' do
expect(subject.body)
.to be_json_eql("Widgets is not set to one of the allowed values.".to_json)
.at_path('_embedded/validationErrors/widgets/message')
end
end
context 'with name set' do
let(:params) do
{
name: 'My custom grid 1',
'_links': {
'scope': {
'href': my_page_path
}
}
}
end
it 'feeds it back' do
expect(subject.body)
.to be_json_eql("My custom grid 1".to_json)
.at_path('_embedded/payload/name')
end
end
context 'with options set' do
let(:params) do
{
options: {
foo: 'bar'
},
'_links': {
'scope': {
'href': my_page_path
}
}
}
end
it 'feeds them back' do
expect(subject.body)
.to be_json_eql("bar".to_json)
.at_path('_embedded/payload/options/foo')
end
end
end
end
@@ -0,0 +1,414 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe 'API v3 Grids resource', type: :request, content_type: :json do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
shared_let(:current_user) do
FactoryBot.create(:user)
end
let(:my_page_grid) { FactoryBot.create(:my_page, user: current_user) }
let(:other_user) do
FactoryBot.create(:user)
end
let(:other_my_page_grid) { FactoryBot.create(:my_page, user: other_user) }
before do
login_as(current_user)
end
subject(:response) { last_response }
describe '#get INDEX' do
let(:path) { api_v3_paths.grids }
let(:stored_grids) do
my_page_grid
other_my_page_grid
end
before do
stored_grids
get path
end
it 'sends a collection of grids but only those visible to the current user' do
expect(subject.body)
.to be_json_eql('Collection'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_embedded/elements/0/_type')
expect(subject.body)
.to be_json_eql(1.to_json)
.at_path('total')
end
context 'with a filter on the scope attribute' do
shared_let(:other_grid) do
grid = Grids::Grid.new(row_count: 20,
column_count: 20)
grid.save
Grids::Grid
.where(id: grid.id)
.update_all(user_id: current_user.id)
grid
end
let(:stored_grids) do
my_page_grid
other_my_page_grid
other_grid
end
let(:path) do
filter = [{ 'scope' =>
{
'operator' => '=',
'values' => [my_page_path]
} }]
"#{api_v3_paths.grids}?#{{ filters: filter.to_json }.to_query}"
end
it 'responds with 200 OK' do
expect(subject.status).to eq(200)
end
it 'sends only the my page of the current user' do
expect(subject.body)
.to be_json_eql('Collection'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_embedded/elements/0/_type')
expect(subject.body)
.to be_json_eql(1.to_json)
.at_path('total')
end
end
end
describe '#get' do
let(:path) { api_v3_paths.grid(my_page_grid.id) }
let(:stored_grids) do
my_page_grid
end
before do
stored_grids
get path
end
it 'responds with 200 OK' do
expect(subject.status).to eq(200)
end
it 'sends a grid block' do
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_type')
end
it 'identifies the url the grid is stored for' do
expect(subject.body)
.to be_json_eql(my_page_path.to_json)
.at_path('_links/scope/href')
end
context 'with the page not existing' do
let(:path) { api_v3_paths.grid(5) }
it 'responds with 404 NOT FOUND' do
expect(subject.status).to eql 404
end
end
context 'with the grid belonging to someone else' do
let(:stored_grids) do
my_page_grid
other_my_page_grid
end
let(:path) { api_v3_paths.grid(other_my_page_grid.id) }
it 'responds with 404 NOT FOUND' do
expect(subject.status).to eql 404
end
end
end
describe '#patch' do
let(:path) { api_v3_paths.grid(my_page_grid.id) }
let(:params) do
{
"rowCount": 10,
"name": 'foo',
"columnCount": 15,
"widgets": [{
"identifier": "work_packages_assigned",
"startRow": 4,
"endRow": 8,
"startColumn": 2,
"endColumn": 5
}]
}.with_indifferent_access
end
let(:stored_grids) do
my_page_grid
end
before do
stored_grids
patch path, params.to_json, 'CONTENT_TYPE' => 'application/json'
end
it 'responds with 200 OK' do
expect(subject.status).to eq(200)
end
it 'returns the altered grid block' do
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql('foo'.to_json)
.at_path('name')
expect(subject.body)
.to be_json_eql(params['rowCount'].to_json)
.at_path('rowCount')
expect(subject.body)
.to be_json_eql(params['widgets'][0]['identifier'].to_json)
.at_path('widgets/0/identifier')
end
it 'perists the changes' do
expect(my_page_grid.reload.row_count)
.to eql params['rowCount']
end
context 'with invalid params' do
let(:params) do
{
"rowCount": -5,
"columnCount": 15,
"widgets": [{
"identifier": "work_packages_assigned",
"startRow": 4,
"endRow": 8,
"startColumn": 2,
"endColumn": 5
}]
}.with_indifferent_access
end
it 'responds with 422 and mentions the error' do
expect(subject.status).to eq 422
expect(subject.body)
.to be_json_eql('Error'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql("Widgets is outside of the grid.".to_json)
.at_path('_embedded/errors/0/message')
expect(subject.body)
.to be_json_eql("Number of rows must be greater than 0.".to_json)
.at_path('_embedded/errors/1/message')
end
it 'does not persist the changes to widgets' do
expect(my_page_grid.reload.widgets.count)
.to eql MyPage::GridRegistration.defaults[:widgets].size
end
end
context 'with a scope param' do
let(:params) do
{
"_links": {
"scope": {
"href": ''
}
}
}.with_indifferent_access
end
it 'responds with 422 and mentions the error' do
expect(subject.status).to eq 422
expect(subject.body)
.to be_json_eql('Error'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql("You must not write a read-only attribute.".to_json)
.at_path('message')
expect(subject.body)
.to be_json_eql("scope".to_json)
.at_path('_embedded/details/attribute')
end
end
context 'with the page not existing' do
let(:path) { api_v3_paths.grid(5) }
it 'responds with 404 NOT FOUND' do
expect(subject.status).to eql 404
end
end
context 'with the grid belonging to someone else' do
let(:stored_grids) do
my_page_grid
other_my_page_grid
end
let(:path) { api_v3_paths.grid(other_my_page_grid.id) }
it 'responds with 404 NOT FOUND' do
expect(subject.status).to eql 404
end
end
end
describe '#post' do
let(:path) { api_v3_paths.grids }
let(:params) do
{
"rowCount": 10,
"columnCount": 15,
"widgets": [{
"identifier": "work_packages_assigned",
"startRow": 4,
"endRow": 8,
"startColumn": 2,
"endColumn": 5
}],
"_links": {
"scope": {
"href": my_page_path
}
}
}.with_indifferent_access
end
before do
post path, params.to_json, 'CONTENT_TYPE' => 'application/json'
end
it 'responds with 201 CREATED' do
expect(subject.status).to eq(201)
end
it 'returns the created grid block' do
expect(subject.body)
.to be_json_eql('Grid'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql(params['rowCount'].to_json)
.at_path('rowCount')
expect(subject.body)
.to be_json_eql(params['widgets'][0]['identifier'].to_json)
.at_path('widgets/0/identifier')
end
it 'persists the grid' do
expect(Grids::Grid.count)
.to eql(1)
end
context 'with invalid params' do
let(:params) do
{
"rowCount": -5,
"columnCount": "sdjfksdfsdfdsf",
"widgets": [{
"identifier": "work_packages_assigned",
"startRow": 4,
"endRow": 8,
"startColumn": 2,
"endColumn": 5
}],
"_links": {
"scope": {
"href": my_page_path
}
}
}.with_indifferent_access
end
it 'responds with 422' do
expect(subject.status).to eq(422)
end
it 'does not create a grid' do
expect(Grids::Grid.count)
.to eql(0)
end
it 'returns the errors' do
expect(subject.body)
.to be_json_eql('Error'.to_json)
.at_path('_type')
expect(subject.body)
.to be_json_eql("Widgets is outside of the grid.".to_json)
.at_path('_embedded/errors/0/message')
expect(subject.body)
.to be_json_eql("Number of rows must be greater than 0.".to_json)
.at_path('_embedded/errors/1/message')
expect(subject.body)
.to be_json_eql("Number of columns must be greater than 0.".to_json)
.at_path('_embedded/errors/2/message')
end
end
end
end
@@ -0,0 +1,174 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe "PATCH /api/v3/grids/:id/form", type: :request, content_type: :json do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
shared_let(:current_user) do
FactoryBot.create(:user)
end
let(:grid) do
FactoryBot.create(:my_page, user: current_user)
end
let(:path) { api_v3_paths.grid_form(grid.id) }
let(:params) { {} }
subject(:response) { last_response }
before do
login_as(current_user)
end
describe '#post' do
before do
post path, params.to_json, 'CONTENT_TYPE' => 'application/json'
end
it 'returns 200 OK' do
expect(subject.status)
.to eql 200
end
it 'is of type form' do
expect(subject.body)
.to be_json_eql("Form".to_json)
.at_path('_type')
end
it 'contains a Schema disallowing setting scope' do
expect(subject.body)
.to be_json_eql("Schema".to_json)
.at_path('_embedded/schema/_type')
expect(subject.body)
.to be_json_eql(false.to_json)
.at_path('_embedded/schema/scope/writable')
end
it 'contains the current data in the payload' do
expected = {
rowCount: 7,
columnCount: 4,
options: {},
widgets: [
{
"_type": "GridWidget",
identifier: 'work_packages_assigned',
options: {},
startRow: 1,
endRow: 7,
startColumn: 1,
endColumn: 3
},
{
"_type": "GridWidget",
identifier: 'work_packages_created',
options: {},
startRow: 1,
endRow: 7,
startColumn: 3,
endColumn: 5
}
],
"_links": {
"scope": {
"href": "/my/page",
"type": "text/html"
}
}
}
expect(subject.body)
.to be_json_eql(expected.to_json)
.at_path('_embedded/payload')
end
it 'has a commit link' do
expect(subject.body)
.to be_json_eql(api_v3_paths.grid(grid.id).to_json)
.at_path('_links/commit/href')
end
context 'with some value for the scope value' do
let(:params) do
{
'_links': {
'scope': {
'href': '/some/path'
}
}
}
end
it 'has a validation error on scope as the value is not writeable' do
expect(subject.body)
.to be_json_eql("You must not write a read-only attribute.".to_json)
.at_path('_embedded/validationErrors/scope/message')
end
end
context 'with an unsupported widget identifier' do
let(:params) do
{
"widgets": [
{
"_type": "GridWidget",
"identifier": "bogus_identifier",
"startRow": 4,
"endRow": 5,
"startColumn": 1,
"endColumn": 2
}
]
}
end
it 'has a validationError on widget' do
expect(subject.body)
.to be_json_eql("Widgets is not set to one of the allowed values.".to_json)
.at_path('_embedded/validationErrors/widgets/message')
end
end
context 'for another user\'s grid' do
let(:other_user) { FactoryBot.create(:user) }
let(:other_grid) { FactoryBot.create(:my_page, user: other_user) }
let(:path) { api_v3_paths.grid_form(other_grid.id) }
it 'returns 404 NOT FOUND' do
expect(subject.status)
.to eql 404
end
end
end
end