commit
e2ab9d4dad
46 changed files with 570 additions and 303 deletions
@ -1,11 +0,0 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
|
||||
const Skeleton = ({ width, height }) => <span className='skeleton' style={{ width, height }}>‌</span>; |
||||
|
||||
Skeleton.propTypes = { |
||||
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), |
||||
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), |
||||
}; |
||||
|
||||
export default Skeleton; |
@ -0,0 +1,12 @@ |
||||
import React from 'react'; |
||||
|
||||
interface Props { |
||||
width?: number | string; |
||||
height?: number | string; |
||||
} |
||||
|
||||
export const Skeleton: React.FC<Props> = ({ width, height }) => ( |
||||
<span className='skeleton' style={{ width, height }}> |
||||
‌ |
||||
</span> |
||||
); |
@ -1,18 +0,0 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
|
||||
const TimelineHint = ({ resource, url }) => ( |
||||
<div className='timeline-hint'> |
||||
<strong><FormattedMessage id='timeline_hint.remote_resource_not_displayed' defaultMessage='{resource} from other servers are not displayed.' values={{ resource }} /></strong> |
||||
<br /> |
||||
<a href={url} target='_blank' rel='noopener'><FormattedMessage id='account.browse_more_on_origin_server' defaultMessage='Browse more on the original profile' /></a> |
||||
</div> |
||||
); |
||||
|
||||
TimelineHint.propTypes = { |
||||
resource: PropTypes.node.isRequired, |
||||
url: PropTypes.string.isRequired, |
||||
}; |
||||
|
||||
export default TimelineHint; |
@ -0,0 +1,27 @@ |
||||
import React from 'react'; |
||||
|
||||
import { FormattedMessage } from 'react-intl'; |
||||
|
||||
interface Props { |
||||
resource: JSX.Element; |
||||
url: string; |
||||
} |
||||
|
||||
export const TimelineHint: React.FC<Props> = ({ resource, url }) => ( |
||||
<div className='timeline-hint'> |
||||
<strong> |
||||
<FormattedMessage |
||||
id='timeline_hint.remote_resource_not_displayed' |
||||
defaultMessage='{resource} from other servers are not displayed.' |
||||
values={{ resource }} |
||||
/> |
||||
</strong> |
||||
<br /> |
||||
<a href={url} target='_blank' rel='noopener noreferrer'> |
||||
<FormattedMessage |
||||
id='account.browse_more_on_origin_server' |
||||
defaultMessage='Browse more on the original profile' |
||||
/> |
||||
</a> |
||||
</div> |
||||
); |
@ -1,23 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
describe Api::V1::FeaturedTagsController do |
||||
render_views |
||||
|
||||
let(:user) { Fabricate(:user) } |
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } |
||||
let(:account) { Fabricate(:account) } |
||||
|
||||
before do |
||||
allow(controller).to receive(:doorkeeper_token) { token } |
||||
end |
||||
|
||||
describe 'GET #index' do |
||||
it 'returns http success' do |
||||
get :index, params: { account_id: account.id, limit: 2 } |
||||
|
||||
expect(response).to have_http_status(200) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,35 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
describe 'email confirmation flow when captcha is enabled' do |
||||
let(:user) { Fabricate(:user, confirmed_at: nil, confirmation_token: 'foobar', created_by_application: client_app) } |
||||
let(:client_app) { nil } |
||||
|
||||
before do |
||||
# rubocop:disable RSpec/AnyInstance -- easiest way to deal with that that I know of |
||||
allow_any_instance_of(Auth::ConfirmationsController).to receive(:captcha_enabled?).and_return(true) |
||||
allow_any_instance_of(Auth::ConfirmationsController).to receive(:check_captcha!).and_return(true) |
||||
allow_any_instance_of(Auth::ConfirmationsController).to receive(:render_captcha).and_return(nil) |
||||
# rubocop:enable RSpec/AnyInstance |
||||
end |
||||
|
||||
context 'when the user signed up through an app' do |
||||
let(:client_app) { Fabricate(:application) } |
||||
|
||||
it 'logs in' do |
||||
visit "/auth/confirmation?confirmation_token=#{user.confirmation_token}&redirect_to_app=true" |
||||
|
||||
# It presents the user with a captcha form |
||||
expect(page).to have_title(I18n.t('auth.captcha_confirmation.title')) |
||||
|
||||
# It does not confirm the user just yet |
||||
expect(user.reload.confirmed?).to be false |
||||
|
||||
# It redirects to app and confirms user |
||||
click_on I18n.t('challenge.confirm') |
||||
expect(user.reload.confirmed?).to be true |
||||
expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,201 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe 'FeaturedTags' do |
||||
let(:user) { Fabricate(:user) } |
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } |
||||
let(:scopes) { 'read:accounts write:accounts' } |
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } |
||||
|
||||
shared_examples 'forbidden for wrong scope' do |wrong_scope| |
||||
let(:scopes) { wrong_scope } |
||||
|
||||
it 'returns http forbidden' do |
||||
expect(response).to have_http_status(403) |
||||
end |
||||
end |
||||
|
||||
describe 'GET /api/v1/featured_tags' do |
||||
context 'with wrong scope' do |
||||
before do |
||||
get '/api/v1/featured_tags', headers: headers |
||||
end |
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read:statuses' |
||||
end |
||||
|
||||
context 'when Authorization header is missing' do |
||||
it 'returns http unauthorized' do |
||||
get '/api/v1/featured_tags' |
||||
|
||||
expect(response).to have_http_status(401) |
||||
end |
||||
end |
||||
|
||||
it 'returns http success' do |
||||
get '/api/v1/featured_tags', headers: headers |
||||
|
||||
expect(response).to have_http_status(200) |
||||
end |
||||
|
||||
context 'when the requesting user has no featured tag' do |
||||
before { Fabricate.times(3, :featured_tag) } |
||||
|
||||
it 'returns an empty body' do |
||||
get '/api/v1/featured_tags', headers: headers |
||||
|
||||
body = body_as_json |
||||
|
||||
expect(body).to be_empty |
||||
end |
||||
end |
||||
|
||||
context 'when the requesting user has featured tags' do |
||||
let!(:user_featured_tags) { Fabricate.times(5, :featured_tag, account: user.account) } |
||||
|
||||
it 'returns only the featured tags belonging to the requesting user' do |
||||
get '/api/v1/featured_tags', headers: headers |
||||
|
||||
body = body_as_json |
||||
expected_ids = user_featured_tags.pluck(:id).map(&:to_s) |
||||
|
||||
expect(body.pluck(:id)).to match_array(expected_ids) |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe 'POST /api/v1/featured_tags' do |
||||
let(:params) { { name: 'tag' } } |
||||
|
||||
it 'returns http success' do |
||||
post '/api/v1/featured_tags', headers: headers, params: params |
||||
|
||||
expect(response).to have_http_status(200) |
||||
end |
||||
|
||||
it 'returns the correct tag name' do |
||||
post '/api/v1/featured_tags', headers: headers, params: params |
||||
|
||||
body = body_as_json |
||||
|
||||
expect(body[:name]).to eq(params[:name]) |
||||
end |
||||
|
||||
it 'creates a new featured tag for the requesting user' do |
||||
post '/api/v1/featured_tags', headers: headers, params: params |
||||
|
||||
featured_tag = FeaturedTag.find_by(name: params[:name], account: user.account) |
||||
|
||||
expect(featured_tag).to be_present |
||||
end |
||||
|
||||
context 'with wrong scope' do |
||||
before do |
||||
post '/api/v1/featured_tags', headers: headers, params: params |
||||
end |
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read:statuses' |
||||
end |
||||
|
||||
context 'when Authorization header is missing' do |
||||
it 'returns http unauthorized' do |
||||
post '/api/v1/featured_tags', params: params |
||||
|
||||
expect(response).to have_http_status(401) |
||||
end |
||||
end |
||||
|
||||
context 'when required param "name" is not provided' do |
||||
it 'returns http bad request' do |
||||
post '/api/v1/featured_tags', headers: headers |
||||
|
||||
expect(response).to have_http_status(400) |
||||
end |
||||
end |
||||
|
||||
context 'when provided tag name is invalid' do |
||||
let(:params) { { name: 'asj&*!' } } |
||||
|
||||
it 'returns http unprocessable entity' do |
||||
post '/api/v1/featured_tags', headers: headers, params: params |
||||
|
||||
expect(response).to have_http_status(422) |
||||
end |
||||
end |
||||
|
||||
context 'when tag name is already taken' do |
||||
before do |
||||
FeaturedTag.create(name: params[:name], account: user.account) |
||||
end |
||||
|
||||
it 'returns http unprocessable entity' do |
||||
post '/api/v1/featured_tags', headers: headers, params: params |
||||
|
||||
expect(response).to have_http_status(422) |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe 'DELETE /api/v1/featured_tags' do |
||||
let!(:featured_tag) { FeaturedTag.create(name: 'tag', account: user.account) } |
||||
let(:id) { featured_tag.id } |
||||
|
||||
it 'returns http success' do |
||||
delete "/api/v1/featured_tags/#{id}", headers: headers |
||||
|
||||
expect(response).to have_http_status(200) |
||||
end |
||||
|
||||
it 'returns an empty body' do |
||||
delete "/api/v1/featured_tags/#{id}", headers: headers |
||||
|
||||
body = body_as_json |
||||
|
||||
expect(body).to be_empty |
||||
end |
||||
|
||||
it 'deletes the featured tag' do |
||||
delete "/api/v1/featured_tags/#{id}", headers: headers |
||||
|
||||
featured_tag = FeaturedTag.find_by(id: id) |
||||
|
||||
expect(featured_tag).to be_nil |
||||
end |
||||
|
||||
context 'with wrong scope' do |
||||
before do |
||||
delete "/api/v1/featured_tags/#{id}", headers: headers |
||||
end |
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read:statuses' |
||||
end |
||||
|
||||
context 'when Authorization header is missing' do |
||||
it 'returns http unauthorized' do |
||||
delete "/api/v1/featured_tags/#{id}" |
||||
|
||||
expect(response).to have_http_status(401) |
||||
end |
||||
end |
||||
|
||||
context 'when featured tag with given id does not exist' do |
||||
it 'returns http not found' do |
||||
delete '/api/v1/featured_tags/0', headers: headers |
||||
|
||||
expect(response).to have_http_status(404) |
||||
end |
||||
end |
||||
|
||||
context 'when deleting a featured tag of another user' do |
||||
let!(:other_user_featured_tag) { Fabricate(:featured_tag) } |
||||
let(:id) { other_user_featured_tag.id } |
||||
|
||||
it 'returns http not found' do |
||||
delete "/api/v1/featured_tags/#{id}", headers: headers |
||||
|
||||
expect(response).to have_http_status(404) |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue