Merge pull request #263 from kmycode/upstream-20231109
Upstream 20231109
This commit is contained in:
commit
4cedcf6132
65 changed files with 18502 additions and 13905 deletions
|
@ -4,7 +4,7 @@ FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye
|
||||||
# Install Rails
|
# Install Rails
|
||||||
# RUN gem install rails webdrivers
|
# RUN gem install rails webdrivers
|
||||||
|
|
||||||
ARG NODE_VERSION="16"
|
ARG NODE_VERSION="20"
|
||||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
|
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
|
||||||
|
|
||||||
# [Optional] Uncomment this section to install additional OS packages.
|
# [Optional] Uncomment this section to install additional OS packages.
|
||||||
|
|
|
@ -11,7 +11,7 @@ bundle install
|
||||||
git checkout -- Gemfile.lock
|
git checkout -- Gemfile.lock
|
||||||
|
|
||||||
# Fetch Javascript dependencies
|
# Fetch Javascript dependencies
|
||||||
yarn --frozen-lockfile
|
yarn install --immutable
|
||||||
|
|
||||||
# [re]create, migrate, and seed the test database
|
# [re]create, migrate, and seed the test database
|
||||||
RAILS_ENV=test ./bin/rails db:setup
|
RAILS_ENV=test ./bin/rails db:setup
|
||||||
|
|
27
.github/actions/setup-javascript/action.yml
vendored
27
.github/actions/setup-javascript/action.yml
vendored
|
@ -11,9 +11,32 @@ runs:
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: yarn
|
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
|
# The following is needed because we can not use `cache: true` for `setup-node`, as it does not support Corepack yet and mess up with the cache location if ran after Node is installed
|
||||||
|
- name: Enable corepack
|
||||||
|
shell: bash
|
||||||
|
run: corepack enable
|
||||||
|
|
||||||
|
- name: Get yarn cache directory path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
shell: bash
|
||||||
|
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Install all yarn packages
|
- name: Install all yarn packages
|
||||||
shell: bash
|
shell: bash
|
||||||
run: yarn --frozen-lockfile ${{ inputs.onlyProduction != 'false' && '--production' || '' }}
|
run: yarn install --immutable
|
||||||
|
if: inputs.onlyProduction == 'false'
|
||||||
|
|
||||||
|
- name: Install all production yarn packages
|
||||||
|
shell: bash
|
||||||
|
run: yarn workspaces focus --production
|
||||||
|
if: inputs.onlyProduction != 'false'
|
||||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -55,6 +55,15 @@ npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
yarn-debug.log
|
yarn-debug.log
|
||||||
|
|
||||||
|
# From https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
# Ignore vagrant log files
|
# Ignore vagrant log files
|
||||||
*-cloudimg-console.log
|
*-cloudimg-console.log
|
||||||
|
|
||||||
|
|
|
@ -71,26 +71,6 @@ RSpec/AnyInstance:
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
Max: 22
|
Max: 22
|
||||||
|
|
||||||
# Configuration parameters: AssignmentOnly.
|
|
||||||
RSpec/InstanceVariable:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/confirmations_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/passwords_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
|
||||||
- 'spec/controllers/concerns/export_controller_concern_spec.rb'
|
|
||||||
- 'spec/controllers/home_controller_spec.rb'
|
|
||||||
- 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb'
|
|
||||||
- 'spec/controllers/statuses_cleanup_controller_spec.rb'
|
|
||||||
- 'spec/models/concerns/account_finder_concern_spec.rb'
|
|
||||||
- 'spec/models/concerns/account_interactions_spec.rb'
|
|
||||||
- 'spec/models/public_feed_spec.rb'
|
|
||||||
- 'spec/serializers/activitypub/note_serializer_spec.rb'
|
|
||||||
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
|
|
||||||
- 'spec/services/remove_status_service_spec.rb'
|
|
||||||
- 'spec/services/search_service_spec.rb'
|
|
||||||
- 'spec/services/unblock_domain_service_spec.rb'
|
|
||||||
|
|
||||||
RSpec/LetSetup:
|
RSpec/LetSetup:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
|
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
|
||||||
|
@ -558,14 +538,6 @@ Style/SingleArgumentDig:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/webpacker/manifest_extensions.rb'
|
- 'lib/webpacker/manifest_extensions.rb'
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
|
||||||
# Configuration parameters: EnforcedStyle.
|
|
||||||
# SupportedStyles: require_parentheses, require_no_parentheses
|
|
||||||
Style/StabbyLambdaParentheses:
|
|
||||||
Exclude:
|
|
||||||
- 'config/environments/production.rb'
|
|
||||||
- 'config/initializers/content_security_policy.rb'
|
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
Style/StderrPuts:
|
Style/StderrPuts:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
@ -625,5 +597,3 @@ Style/TrailingCommaInHashLiteral:
|
||||||
Style/WordArray:
|
Style/WordArray:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/helpers/languages_helper.rb'
|
- 'app/helpers/languages_helper.rb'
|
||||||
- 'spec/controllers/settings/imports_controller_spec.rb'
|
|
||||||
- 'spec/models/form/import_spec.rb'
|
|
||||||
|
|
0
.yarn/.gitkeep
Normal file
0
.yarn/.gitkeep
Normal file
13
.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch
Normal file
13
.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
diff --git a/lib/index.js b/lib/index.js
|
||||||
|
index 16ed6be8be8f555cc99096c2ff60954b42dc313d..d009c069770d066ad0db7ad02de1ea473a29334e 100644
|
||||||
|
--- a/lib/index.js
|
||||||
|
+++ b/lib/index.js
|
||||||
|
@@ -99,7 +99,7 @@ function lodash(_ref) {
|
||||||
|
|
||||||
|
var node = _ref3;
|
||||||
|
|
||||||
|
- if ((0, _types.isModuleDeclaration)(node)) {
|
||||||
|
+ if ((0, _types.isImportDeclaration)(node) || (0, _types.isExportDeclaration)(node)) {
|
||||||
|
isModule = true;
|
||||||
|
break;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
diff --git a/dist/index.js b/dist/index.js
|
||||||
|
index 57e375592d984e9a429bcd9f800fa2d15cd662e4..0c47d96df3608e23adfd77d887a8f72abbd501c0 100644
|
||||||
|
--- a/dist/index.js
|
||||||
|
+++ b/dist/index.js
|
||||||
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
||||||
|
});
|
||||||
|
exports.default = void 0;
|
||||||
|
|
||||||
|
-var _crypto = _interopRequireDefault(require("crypto"));
|
||||||
|
+var _createHash = _interopRequireDefault(require("webpack/lib/util/createHash"));
|
||||||
|
|
||||||
|
var _path = _interopRequireDefault(require("path"));
|
||||||
|
|
||||||
|
@@ -227,7 +227,7 @@ class CompressionPlugin {
|
||||||
|
originalAlgorithm: this.options.algorithm,
|
||||||
|
compressionOptions: this.options.compressionOptions,
|
||||||
|
name,
|
||||||
|
- contentHash: _crypto.default.createHash("md4").update(input).digest("hex")
|
||||||
|
+ contentHash: _createHash.default("md4").update(input).digest("hex")
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
cacheData.name = (0, _serializeJavascript.default)({
|
49
.yarnclean
49
.yarnclean
|
@ -1,49 +0,0 @@
|
||||||
# test directories
|
|
||||||
__tests__
|
|
||||||
test
|
|
||||||
tests
|
|
||||||
powered-test
|
|
||||||
|
|
||||||
# asset directories
|
|
||||||
docs
|
|
||||||
doc
|
|
||||||
website
|
|
||||||
images
|
|
||||||
# assets
|
|
||||||
|
|
||||||
# examples
|
|
||||||
example
|
|
||||||
examples
|
|
||||||
|
|
||||||
# code coverage directories
|
|
||||||
coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# build scripts
|
|
||||||
Makefile
|
|
||||||
Gulpfile.js
|
|
||||||
Gruntfile.js
|
|
||||||
|
|
||||||
# configs
|
|
||||||
.tern-project
|
|
||||||
.gitattributes
|
|
||||||
.editorconfig
|
|
||||||
.*ignore
|
|
||||||
.eslintrc
|
|
||||||
.jshintrc
|
|
||||||
.flowconfig
|
|
||||||
.documentup.json
|
|
||||||
.yarn-metadata.json
|
|
||||||
.*.yml
|
|
||||||
*.yml
|
|
||||||
|
|
||||||
# misc
|
|
||||||
*.gz
|
|
||||||
*.md
|
|
||||||
|
|
||||||
# for specific ignore
|
|
||||||
!.svgo.yml
|
|
||||||
!sass-lint/**/*.yml
|
|
||||||
|
|
||||||
# breaks lint-staged or generally anything using https://github.com/eemeli/yaml/issues/384
|
|
||||||
!**/yaml/dist/**/doc
|
|
1
.yarnrc.yml
Normal file
1
.yarnrc.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
nodeLinker: node-modules
|
14
Dockerfile
14
Dockerfile
|
@ -13,7 +13,6 @@ ENV DEBIAN_FRONTEND="noninteractive" \
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
WORKDIR /opt/mastodon
|
WORKDIR /opt/mastodon
|
||||||
COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
|
||||||
|
|
||||||
# hadolint ignore=DL3008
|
# hadolint ignore=DL3008
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
|
@ -36,8 +35,14 @@ RUN apt-get update && \
|
||||||
bundle config set --local deployment 'true' && \
|
bundle config set --local deployment 'true' && \
|
||||||
bundle config set --local without 'development test' && \
|
bundle config set --local without 'development test' && \
|
||||||
bundle config set silence_root_warning true && \
|
bundle config set silence_root_warning true && \
|
||||||
bundle install -j"$(nproc)" && \
|
corepack enable
|
||||||
yarn install --pure-lockfile --production --network-timeout 600000 && \
|
|
||||||
|
COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/
|
||||||
|
COPY .yarn /opt/mastodon/.yarn
|
||||||
|
|
||||||
|
RUN bundle install -j"$(nproc)"
|
||||||
|
|
||||||
|
RUN yarn workspaces focus --all --production && \
|
||||||
yarn cache clean
|
yarn cache clean
|
||||||
|
|
||||||
FROM node:${NODE_VERSION}
|
FROM node:${NODE_VERSION}
|
||||||
|
@ -78,7 +83,8 @@ RUN apt-get update && \
|
||||||
tzdata \
|
tzdata \
|
||||||
libreadline8 \
|
libreadline8 \
|
||||||
tini && \
|
tini && \
|
||||||
ln -s /opt/mastodon /mastodon
|
ln -s /opt/mastodon /mastodon && \
|
||||||
|
corepack enable
|
||||||
|
|
||||||
# Note: no, cleaning here since Debian does this automatically
|
# Note: no, cleaning here since Debian does this automatically
|
||||||
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem
|
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem
|
||||||
|
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
|
@ -112,7 +112,7 @@ bundle install
|
||||||
|
|
||||||
# Install node modules
|
# Install node modules
|
||||||
sudo corepack enable
|
sudo corepack enable
|
||||||
yarn set version classic
|
corepack prepare
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
# Build Mastodon
|
# Build Mastodon
|
||||||
|
|
|
@ -86,6 +86,11 @@ module Admin
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def batched_ordered_status_edits
|
||||||
|
@status.edits.reorder(nil).includes(:account, status: [:account]).find_each(order: :asc)
|
||||||
|
end
|
||||||
|
helper_method :batched_ordered_status_edits
|
||||||
|
|
||||||
def admin_status_batch_action_params
|
def admin_status_batch_action_params
|
||||||
params.require(:admin_status_batch_action).permit(status_ids: [])
|
params.require(:admin_status_batch_action).permit(status_ids: [])
|
||||||
end
|
end
|
||||||
|
|
|
@ -298,5 +298,3 @@ module LanguagesHelper
|
||||||
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
|
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:enable Metrics/ModuleLength
|
|
||||||
|
|
80
app/javascript/mastodon/features/__tests__/toggle-play.jsx
Normal file
80
app/javascript/mastodon/features/__tests__/toggle-play.jsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
|
|
||||||
|
class Media extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
paused: props.paused || false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMediaClick = () => {
|
||||||
|
const { onClick } = this.props;
|
||||||
|
|
||||||
|
this.setState(prevState => ({
|
||||||
|
paused: !prevState.paused,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (typeof onClick === 'function') {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title } = this.props;
|
||||||
|
const mediaElements = document.querySelectorAll(`div[title="${title}"]`);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
mediaElements.forEach(element => {
|
||||||
|
if (element !== this && !element.classList.contains('paused')) {
|
||||||
|
element.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { title } = this.props;
|
||||||
|
const { paused } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button title={title} onClick={this.handleMediaClick}>
|
||||||
|
Media Component - {paused ? 'Paused' : 'Playing'}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Media.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
paused: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Media attachments test', () => {
|
||||||
|
let currentMedia = null;
|
||||||
|
const togglePlayMock = jest.fn();
|
||||||
|
|
||||||
|
it('plays a new media file and pauses others that were playing', () => {
|
||||||
|
const container = render(
|
||||||
|
<div>
|
||||||
|
<Media title='firstMedia' paused onClick={togglePlayMock} />
|
||||||
|
<Media title='secondMedia' paused onClick={togglePlayMock} />
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(container.getByTitle('firstMedia'));
|
||||||
|
expect(togglePlayMock).toHaveBeenCalledTimes(1);
|
||||||
|
currentMedia = container.getByTitle('firstMedia');
|
||||||
|
expect(currentMedia.textContent).toMatch(/Playing/);
|
||||||
|
|
||||||
|
fireEvent.click(container.getByTitle('secondMedia'));
|
||||||
|
expect(togglePlayMock).toHaveBeenCalledTimes(2);
|
||||||
|
currentMedia = container.getByTitle('secondMedia');
|
||||||
|
expect(currentMedia.textContent).toMatch(/Playing/);
|
||||||
|
});
|
||||||
|
});
|
|
@ -20,6 +20,7 @@ import { formatTime, getPointerPosition, fileNameFromURL } from 'mastodon/featur
|
||||||
|
|
||||||
import { Blurhash } from '../../components/blurhash';
|
import { Blurhash } from '../../components/blurhash';
|
||||||
import { displayMedia, useBlurhash } from '../../initial_state';
|
import { displayMedia, useBlurhash } from '../../initial_state';
|
||||||
|
import { currentMedia, setCurrentMedia } from '../../reducers/media_attachments';
|
||||||
|
|
||||||
import Visualizer from './visualizer';
|
import Visualizer from './visualizer';
|
||||||
|
|
||||||
|
@ -165,15 +166,32 @@ class Audio extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePlay = () => {
|
togglePlay = () => {
|
||||||
if (!this.audioContext) {
|
const audios = document.querySelectorAll('audio');
|
||||||
this._initAudioContext();
|
|
||||||
|
audios.forEach((audio) => {
|
||||||
|
const button = audio.previousElementSibling;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
if(audio.paused) {
|
||||||
|
audios.forEach((e) => {
|
||||||
|
if (e !== audio) {
|
||||||
|
e.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
audio.play();
|
||||||
|
this.setState({ paused: false });
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
this.setState({ paused: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentMedia !== null) {
|
||||||
|
currentMedia.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.paused) {
|
this.audio.play();
|
||||||
this.setState({ paused: false }, () => this.audio.play());
|
setCurrentMedia(this.audio);
|
||||||
} else {
|
|
||||||
this.setState({ paused: true }, () => this.audio.pause());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleResize = debounce(() => {
|
handleResize = debounce(() => {
|
||||||
|
@ -195,6 +213,7 @@ class Audio extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePause = () => {
|
handlePause = () => {
|
||||||
|
this.audio.pause();
|
||||||
this.setState({ paused: true });
|
this.setState({ paused: true });
|
||||||
|
|
||||||
if (this.audioContext) {
|
if (this.audioContext) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ class CircleSelect extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
unavailable: PropTypes.bool,
|
unavailable: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
circles: ImmutablePropTypes.list,
|
circles: ImmutablePropTypes.map,
|
||||||
circleId: PropTypes.string,
|
circleId: PropTypes.string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
|
@ -95,7 +95,7 @@ const makeMapStateToProps = () => {
|
||||||
const getPictureInPicture = makeGetPictureInPicture();
|
const getPictureInPicture = makeGetPictureInPicture();
|
||||||
|
|
||||||
const getReferenceIds = createSelector([
|
const getReferenceIds = createSelector([
|
||||||
(state, { id }) => state.getIn(['contexts', 'references', id]),
|
(state, { id }) => state.getIn(['contexts', 'references', id]) || Immutable.List(),
|
||||||
], (references) => {
|
], (references) => {
|
||||||
return references;
|
return references;
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,7 +27,7 @@ const Account = connect(state => ({
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
|
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
|
||||||
reload: { id: 'navigation_bar.reload', defaultMessage: 'Reload' },
|
reload: { id: 'navigation_bar.refresh', defaultMessage: 'Refresh' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { Icon } from 'mastodon/components/icon';
|
||||||
import { playerSettings } from 'mastodon/settings';
|
import { playerSettings } from 'mastodon/settings';
|
||||||
|
|
||||||
import { displayMedia, useBlurhash } from '../../initial_state';
|
import { displayMedia, useBlurhash } from '../../initial_state';
|
||||||
|
import { currentMedia, setCurrentMedia } from '../../reducers/media_attachments';
|
||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -181,6 +182,7 @@ class Video extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePause = () => {
|
handlePause = () => {
|
||||||
|
this.video.pause();
|
||||||
this.setState({ paused: true });
|
this.setState({ paused: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -344,11 +346,32 @@ class Video extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
togglePlay = () => {
|
togglePlay = () => {
|
||||||
if (this.state.paused) {
|
const videos = document.querySelectorAll('video');
|
||||||
this.setState({ paused: false }, () => this.video.play());
|
|
||||||
} else {
|
videos.forEach((video) => {
|
||||||
this.setState({ paused: true }, () => this.video.pause());
|
const button = video.nextElementSibling;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
if (video.paused) {
|
||||||
|
videos.forEach((e) => {
|
||||||
|
if (e !== video) {
|
||||||
|
e.pause();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
video.play();
|
||||||
|
this.setState({ paused: false });
|
||||||
|
} else {
|
||||||
|
video.pause();
|
||||||
|
this.setState({ paused: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentMedia !== null) {
|
||||||
|
currentMedia.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.video.play();
|
||||||
|
setCurrentMedia(this.video);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleFullscreen = () => {
|
toggleFullscreen = () => {
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
"attachments_list.unprocessed": "(ausstehend)",
|
"attachments_list.unprocessed": "(ausstehend)",
|
||||||
"audio.hide": "Audio ausblenden",
|
"audio.hide": "Audio ausblenden",
|
||||||
"autosuggest_hashtag.per_week": "{count} pro Woche",
|
"autosuggest_hashtag.per_week": "{count} pro Woche",
|
||||||
"boost_modal.combo": "Drücke {combo}, um das beim nächsten Mal zu überspringen",
|
"boost_modal.combo": "Mit {combo} wird dieses Fenster beim nächsten Mal nicht mehr angezeigt",
|
||||||
"bundle_column_error.copy_stacktrace": "Fehlerbericht kopieren",
|
"bundle_column_error.copy_stacktrace": "Fehlerbericht kopieren",
|
||||||
"bundle_column_error.error.body": "Die angeforderte Seite konnte nicht dargestellt werden. Dies könnte auf einen Fehler in unserem Code oder auf ein Browser-Kompatibilitätsproblem zurückzuführen sein.",
|
"bundle_column_error.error.body": "Die angeforderte Seite konnte nicht dargestellt werden. Dies könnte auf einen Fehler in unserem Code oder auf ein Browser-Kompatibilitätsproblem zurückzuführen sein.",
|
||||||
"bundle_column_error.error.title": "Oh nein!",
|
"bundle_column_error.error.title": "Oh nein!",
|
||||||
|
|
|
@ -691,6 +691,7 @@
|
||||||
"status.media.show": "Click to show",
|
"status.media.show": "Click to show",
|
||||||
"status.media_hidden": "Media hidden",
|
"status.media_hidden": "Media hidden",
|
||||||
"status.mention": "Mention @{name}",
|
"status.mention": "Mention @{name}",
|
||||||
|
"status.mentions": "Mentioned accounts",
|
||||||
"status.more": "More",
|
"status.more": "More",
|
||||||
"status.mute": "Mute @{name}",
|
"status.mute": "Mute @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
"account.share": "שתף את הפרופיל של @{name}",
|
"account.share": "שתף את הפרופיל של @{name}",
|
||||||
"account.show_reblogs": "הצג הדהודים מאת @{name}",
|
"account.show_reblogs": "הצג הדהודים מאת @{name}",
|
||||||
"account.statuses_counter": "{count, plural, one {הודעה} two {הודעותיים} many {{count} הודעות} other {{count} הודעות}}",
|
"account.statuses_counter": "{count, plural, one {הודעה} two {הודעותיים} many {{count} הודעות} other {{count} הודעות}}",
|
||||||
"account.unblock": "הסר את החסימה של @{name}",
|
"account.unblock": "להסיר חסימה ל- @{name}",
|
||||||
"account.unblock_domain": "הסירי את החסימה של קהילת {domain}",
|
"account.unblock_domain": "הסירי את החסימה של קהילת {domain}",
|
||||||
"account.unblock_short": "הסר חסימה",
|
"account.unblock_short": "הסר חסימה",
|
||||||
"account.unendorse": "אל תקדם בפרופיל",
|
"account.unendorse": "אל תקדם בפרופיל",
|
||||||
|
|
|
@ -777,6 +777,7 @@
|
||||||
"status.media.show": "クリックして表示",
|
"status.media.show": "クリックして表示",
|
||||||
"status.media_hidden": "非表示のメディア",
|
"status.media_hidden": "非表示のメディア",
|
||||||
"status.mention": "@{name}さんに投稿",
|
"status.mention": "@{name}さんに投稿",
|
||||||
|
"status.mentions": "メンション先一覧",
|
||||||
"status.more": "もっと見る",
|
"status.more": "もっと見る",
|
||||||
"status.mute": "@{name}さんをミュート",
|
"status.mute": "@{name}さんをミュート",
|
||||||
"status.mute_conversation": "会話をミュート",
|
"status.mute_conversation": "会話をミュート",
|
||||||
|
|
|
@ -222,7 +222,7 @@
|
||||||
"emoji_button.search_results": "Výsledky hľadania",
|
"emoji_button.search_results": "Výsledky hľadania",
|
||||||
"emoji_button.symbols": "Symboly",
|
"emoji_button.symbols": "Symboly",
|
||||||
"emoji_button.travel": "Cestovanie a miesta",
|
"emoji_button.travel": "Cestovanie a miesta",
|
||||||
"empty_column.account_suspended": "Účet bol vylúčený",
|
"empty_column.account_suspended": "Účet bol pozastavený",
|
||||||
"empty_column.account_timeline": "Nie sú tu žiadne príspevky!",
|
"empty_column.account_timeline": "Nie sú tu žiadne príspevky!",
|
||||||
"empty_column.account_unavailable": "Profil nedostupný",
|
"empty_column.account_unavailable": "Profil nedostupný",
|
||||||
"empty_column.blocks": "Ešte si nikoho nezablokoval/a.",
|
"empty_column.blocks": "Ešte si nikoho nezablokoval/a.",
|
||||||
|
|
|
@ -2,6 +2,13 @@ import { Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
import { STORE_HYDRATE } from '../actions/store';
|
import { STORE_HYDRATE } from '../actions/store';
|
||||||
|
|
||||||
|
export let currentMedia = null;
|
||||||
|
|
||||||
|
export function setCurrentMedia(value) {
|
||||||
|
currentMedia = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
accept_content_types: [],
|
accept_content_types: [],
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
%h3= t('admin.statuses.history')
|
%h3= t('admin.statuses.history')
|
||||||
|
|
||||||
%ol.history
|
%ol.history
|
||||||
- @status.edits.reorder(nil).includes(:account, status: [:account]).find_each(order: :asc).with_index do |status_edit, i|
|
- batched_ordered_status_edits.with_index do |status_edit, i|
|
||||||
%li
|
%li
|
||||||
.history__entry
|
.history__entry
|
||||||
%h5
|
%h5
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
default: &default
|
default: &default
|
||||||
adapter: postgresql
|
adapter: postgresql
|
||||||
pool: <%= ENV["DB_POOL"] || ENV['MAX_THREADS'] || 5 %>
|
pool: <%= ENV["DB_POOL"] || (if Sidekiq.server? then Sidekiq[:concurrency] else ENV['MAX_THREADS'] end) || 5 %>
|
||||||
timeout: 5000
|
timeout: 5000
|
||||||
connect_timeout: 15
|
connect_timeout: 15
|
||||||
encoding: unicode
|
encoding: unicode
|
||||||
|
|
|
@ -44,7 +44,7 @@ Rails.application.configure do
|
||||||
config.force_ssl = true
|
config.force_ssl = true
|
||||||
config.ssl_options = {
|
config.ssl_options = {
|
||||||
redirect: {
|
redirect: {
|
||||||
exclude: ->request { request.path.start_with?('/health') || request.headers["Host"].end_with?('.onion') || request.headers["Host"].end_with?('.i2p') }
|
exclude: ->(request) { request.path.start_with?('/health') || request.headers["Host"].end_with?('.onion') || request.headers["Host"].end_with?('.i2p') }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ end
|
||||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
||||||
# Rails.application.config.content_security_policy_report_only = true
|
# Rails.application.config.content_security_policy_report_only = true
|
||||||
|
|
||||||
Rails.application.config.content_security_policy_nonce_generator = ->request { SecureRandom.base64(16) }
|
Rails.application.config.content_security_policy_nonce_generator = ->(request) { SecureRandom.base64(16) }
|
||||||
|
|
||||||
Rails.application.config.content_security_policy_nonce_directives = %w(style-src)
|
Rails.application.config.content_security_policy_nonce_directives = %w(style-src)
|
||||||
|
|
||||||
|
|
|
@ -556,6 +556,7 @@ be:
|
||||||
total_reported: Скаргі на іх
|
total_reported: Скаргі на іх
|
||||||
total_storage: Медыя дадаткі
|
total_storage: Медыя дадаткі
|
||||||
totals_time_period_hint_html: Паказаныя агульныя значэнні ніжэй уключаюць даныя за ўвесь час.
|
totals_time_period_hint_html: Паказаныя агульныя значэнні ніжэй уключаюць даныя за ўвесь час.
|
||||||
|
unknown_instance: На дадзены момант няма запісаў аб гэтым дамене на гэтым серверы.
|
||||||
invites:
|
invites:
|
||||||
deactivate_all: Дэактываваць усё
|
deactivate_all: Дэактываваць усё
|
||||||
filter:
|
filter:
|
||||||
|
@ -1076,6 +1077,14 @@ be:
|
||||||
hint_html: Засталася яшчэ адна рэч! Каб не дапусціць спаму, нам трэба пацвердзіць, што вы чалавек. Разгадайце CAPTCHA ніжэй і націсніце «Працягнуць».
|
hint_html: Засталася яшчэ адна рэч! Каб не дапусціць спаму, нам трэба пацвердзіць, што вы чалавек. Разгадайце CAPTCHA ніжэй і націсніце «Працягнуць».
|
||||||
title: Праверка бяспекі
|
title: Праверка бяспекі
|
||||||
confirmations:
|
confirmations:
|
||||||
|
awaiting_review: Ваш электронны адрас пацверджаны! Адміністрацыя %{domain} зараз разглядае вашу рэгістрацыю. Вы атрымаеце паведамленне па электроннай пошце, калі ваш уліковы запіс будзе ўхвалены!
|
||||||
|
awaiting_review_title: Ваша рэгістрацыя разглядаецца
|
||||||
|
clicking_this_link: націснуць на гэту спасылку
|
||||||
|
login_link: увайсці
|
||||||
|
proceed_to_login_html: Цяпер вы можаце перайсці да %{login_link}.
|
||||||
|
redirect_to_app_html: Вы павінны былі быць перанакіраваны ў праграму <strong>%{app_name}</strong>. Калі гэтага не адбылося, паспрабуйце %{clicking_this_link} або вярніцеся да праграмы ўручную.
|
||||||
|
registration_complete: Ваша рэгістрацыя на %{domain} завершана!
|
||||||
|
welcome_title: Вітаем, %{name}!
|
||||||
wrong_email_hint: Калі гэты адрас электроннай пошты памылковы, вы можаце змяніць яго ў наладах уліковага запісу.
|
wrong_email_hint: Калі гэты адрас электроннай пошты памылковы, вы можаце змяніць яго ў наладах уліковага запісу.
|
||||||
delete_account: Выдаліць уліковы запіс
|
delete_account: Выдаліць уліковы запіс
|
||||||
delete_account_html: Калі вы жадаеце выдаліць ваш уліковы запіс, можаце <a href="%{path}">працягнуць тут</a>. Ад вас будзе запатрабавана пацвярджэнне.
|
delete_account_html: Калі вы жадаеце выдаліць ваш уліковы запіс, можаце <a href="%{path}">працягнуць тут</a>. Ад вас будзе запатрабавана пацвярджэнне.
|
||||||
|
@ -1137,6 +1146,7 @@ be:
|
||||||
functional: Ваш уліковы запіс поўнасцю працуе.
|
functional: Ваш уліковы запіс поўнасцю працуе.
|
||||||
pending: Ваша заяўка разглядаецца нашым супрацоўнікам. Гэта можа заняць некаторы час. Вы атрымаеце электронны ліст, калі заяўка будзе ўхвалена.
|
pending: Ваша заяўка разглядаецца нашым супрацоўнікам. Гэта можа заняць некаторы час. Вы атрымаеце электронны ліст, калі заяўка будзе ўхвалена.
|
||||||
redirecting_to: Ваш уліковы запіс неактыўны, бо ў цяперашні час ён перанакіроўваецца на %{acct}.
|
redirecting_to: Ваш уліковы запіс неактыўны, бо ў цяперашні час ён перанакіроўваецца на %{acct}.
|
||||||
|
self_destruct: Паколькі %{domain} зачыняецца, вы атрымаеце толькі абмежаваны доступ да свайго уліковага запісу.
|
||||||
view_strikes: Праглядзець мінулыя папярэджанні для вашага ўліковага запісу
|
view_strikes: Праглядзець мінулыя папярэджанні для вашага ўліковага запісу
|
||||||
too_fast: Форма адпраўлена занадта хутка, паспрабуйце яшчэ раз.
|
too_fast: Форма адпраўлена занадта хутка, паспрабуйце яшчэ раз.
|
||||||
use_security_key: Выкарыстаеце ключ бяспекі
|
use_security_key: Выкарыстаеце ключ бяспекі
|
||||||
|
@ -1622,6 +1632,9 @@ be:
|
||||||
over_daily_limit: Вы перавысілі ліміт ў %{limit} запланаваных на сёння допісаў
|
over_daily_limit: Вы перавысілі ліміт ў %{limit} запланаваных на сёння допісаў
|
||||||
over_total_limit: Вы перавысілі ліміт ў %{limit} запланаваных допісаў
|
over_total_limit: Вы перавысілі ліміт ў %{limit} запланаваных допісаў
|
||||||
too_soon: Запланаваная дата мусіць быць у будучыні
|
too_soon: Запланаваная дата мусіць быць у будучыні
|
||||||
|
self_destruct:
|
||||||
|
lead_html: На жаль, дамен <strong>%{domain}</strong> зачыняецца назаўсёды. Калі ў вас быў уліковы запіс, вы не зможаце працягваць выкарыстоўваць яго, але вы ўсё яшчэ можаце запытаць рэзервовае капіраванне вашых даных.
|
||||||
|
title: Гэты сервер зачыняецца
|
||||||
sessions:
|
sessions:
|
||||||
activity: Апошняя актыўнасць
|
activity: Апошняя актыўнасць
|
||||||
browser: Браўзер
|
browser: Браўзер
|
||||||
|
|
|
@ -1601,7 +1601,7 @@ ko:
|
||||||
windows_mobile: 윈도우 모바일
|
windows_mobile: 윈도우 모바일
|
||||||
windows_phone: 윈도우 폰
|
windows_phone: 윈도우 폰
|
||||||
revoke: 삭제
|
revoke: 삭제
|
||||||
revoke_success: 세션을 성공적으로 취소하였습니다
|
revoke_success: 세션을 성공적으로 삭제하였습니다
|
||||||
title: 세션
|
title: 세션
|
||||||
view_authentication_history: 내 계정에 대한 인증 이력 보기
|
view_authentication_history: 내 계정에 대한 인증 이력 보기
|
||||||
settings:
|
settings:
|
||||||
|
|
|
@ -98,7 +98,7 @@ sk:
|
||||||
disabled: Blokovaný
|
disabled: Blokovaný
|
||||||
pending: Čakajúci
|
pending: Čakajúci
|
||||||
silenced: Obmedzený
|
silenced: Obmedzený
|
||||||
suspended: Vylúčený/á
|
suspended: Pozastavený/á
|
||||||
title: Moderácia
|
title: Moderácia
|
||||||
moderation_notes: Moderátorské poznámky
|
moderation_notes: Moderátorské poznámky
|
||||||
most_recent_activity: Posledná aktivita
|
most_recent_activity: Posledná aktivita
|
||||||
|
@ -149,8 +149,8 @@ sk:
|
||||||
statuses: Príspevkov
|
statuses: Príspevkov
|
||||||
strikes: Predchádzajúce údery
|
strikes: Predchádzajúce údery
|
||||||
subscribe: Odoberaj
|
subscribe: Odoberaj
|
||||||
suspend: Vylúč
|
suspend: Pozastav
|
||||||
suspended: Vylúčený/á
|
suspended: Pozastavený/á
|
||||||
suspension_irreversible: Údaje tohto účtu boli nenávratne vymazané. Účet môžete zrušiť, aby sa dal používať, ale neobnovia sa žiadne údaje, ktoré predtým mal.
|
suspension_irreversible: Údaje tohto účtu boli nenávratne vymazané. Účet môžete zrušiť, aby sa dal používať, ale neobnovia sa žiadne údaje, ktoré predtým mal.
|
||||||
suspension_reversible_hint_html: Účet bol pozastavený a údaje budú úplne odstránené dňa %{date}. Dovtedy je možné účet obnoviť bez akýchkoľvek nepriaznivých účinkov. Ak chcete okamžite odstrániť všetky údaje účtu, môžete tak urobiť nižšie.
|
suspension_reversible_hint_html: Účet bol pozastavený a údaje budú úplne odstránené dňa %{date}. Dovtedy je možné účet obnoviť bez akýchkoľvek nepriaznivých účinkov. Ak chcete okamžite odstrániť všetky údaje účtu, môžete tak urobiť nižšie.
|
||||||
title: Účty
|
title: Účty
|
||||||
|
@ -162,6 +162,7 @@ sk:
|
||||||
undo_suspension: Zruš blokovanie
|
undo_suspension: Zruš blokovanie
|
||||||
unsilenced_msg: Úspešne zrušené obmedzenie účtu %{username}
|
unsilenced_msg: Úspešne zrušené obmedzenie účtu %{username}
|
||||||
unsubscribe: Prestaň odoberať
|
unsubscribe: Prestaň odoberať
|
||||||
|
unsuspended_msg: "%{username} ov/in účet úspešne spojazdnený"
|
||||||
username: Prezývka
|
username: Prezývka
|
||||||
view_domain: Ukáž súhrn pre doménu
|
view_domain: Ukáž súhrn pre doménu
|
||||||
warn: Varuj
|
warn: Varuj
|
||||||
|
@ -209,7 +210,7 @@ sk:
|
||||||
resolve_report: Vyrieš nahlásený problém
|
resolve_report: Vyrieš nahlásený problém
|
||||||
sensitive_account: Vynúť všetky médiá na účte ako chúlostivé
|
sensitive_account: Vynúť všetky médiá na účte ako chúlostivé
|
||||||
silence_account: Utíš účet
|
silence_account: Utíš účet
|
||||||
suspend_account: Vylúč účet
|
suspend_account: Pozastav účet
|
||||||
unassigned_report: Odober priradenie nahlásenia
|
unassigned_report: Odober priradenie nahlásenia
|
||||||
unblock_email_account: Odblokuj emailovú adresu
|
unblock_email_account: Odblokuj emailovú adresu
|
||||||
unsilence_account: Zvráť obmedzenie účtu
|
unsilence_account: Zvráť obmedzenie účtu
|
||||||
|
@ -255,6 +256,7 @@ sk:
|
||||||
silence_account_html: "%{name} obmedzil/a účet %{target}"
|
silence_account_html: "%{name} obmedzil/a účet %{target}"
|
||||||
suspend_account_html: "%{name} zablokoval/a účet používateľa %{target}"
|
suspend_account_html: "%{name} zablokoval/a účet používateľa %{target}"
|
||||||
unassigned_report_html: "%{name} odobral/a report od %{target}"
|
unassigned_report_html: "%{name} odobral/a report od %{target}"
|
||||||
|
unsuspend_account_html: "%{name} spojazdnil/a účet %{target}"
|
||||||
update_user_role_html: "%{name} zmenil/a rolu pre %{target}"
|
update_user_role_html: "%{name} zmenil/a rolu pre %{target}"
|
||||||
deleted_account: zmazaný účet
|
deleted_account: zmazaný účet
|
||||||
empty: Žiadne záznamy nenájdené.
|
empty: Žiadne záznamy nenájdené.
|
||||||
|
@ -341,6 +343,7 @@ sk:
|
||||||
confirm_suspension:
|
confirm_suspension:
|
||||||
cancel: Zruš
|
cancel: Zruš
|
||||||
confirm: Vylúč
|
confirm: Vylúč
|
||||||
|
preamble_html: Chystáš sa vylúčiť <strong>%{domain}</strong> a jej poddomény.
|
||||||
title: Potvrď blokovanie domény %{domain}
|
title: Potvrď blokovanie domény %{domain}
|
||||||
created_msg: Doména je v štádiu blokovania
|
created_msg: Doména je v štádiu blokovania
|
||||||
destroyed_msg: Blokovanie domény bolo zrušené
|
destroyed_msg: Blokovanie domény bolo zrušené
|
||||||
|
@ -355,7 +358,7 @@ sk:
|
||||||
severity:
|
severity:
|
||||||
noop: Nič
|
noop: Nič
|
||||||
silence: Obmedz
|
silence: Obmedz
|
||||||
suspend: Vylúč
|
suspend: Pozastav
|
||||||
title: Nové blokovanie domény
|
title: Nové blokovanie domény
|
||||||
not_permitted: Nemáš povolenie na vykonanie tohto kroku
|
not_permitted: Nemáš povolenie na vykonanie tohto kroku
|
||||||
obfuscate: Zatemniť názov domény
|
obfuscate: Zatemniť názov domény
|
||||||
|
@ -416,7 +419,7 @@ sk:
|
||||||
reject_media: Zamietni médiá
|
reject_media: Zamietni médiá
|
||||||
reject_reports: Zamietni hlásenia
|
reject_reports: Zamietni hlásenia
|
||||||
silence: Obmedzená
|
silence: Obmedzená
|
||||||
suspend: Vylúč
|
suspend: Pozastav
|
||||||
policy: Zásady
|
policy: Zásady
|
||||||
reason: Verejné odôvodnenie
|
reason: Verejné odôvodnenie
|
||||||
title: Zásady o obsahu
|
title: Zásady o obsahu
|
||||||
|
@ -537,7 +540,7 @@ sk:
|
||||||
statuses: Nahlásený obsah
|
statuses: Nahlásený obsah
|
||||||
summary:
|
summary:
|
||||||
action_preambles:
|
action_preambles:
|
||||||
suspend_html: 'Chystáš sa <strong>vylúčiť</strong> účet <strong>@%{acct}</strong>. To urobí:'
|
suspend_html: 'Chystáš sa <strong>pozastaviť</strong> účet <strong>@%{acct}</strong>. To urobí:'
|
||||||
actions:
|
actions:
|
||||||
delete_html: Vymaž pohoršujúce príspevky
|
delete_html: Vymaž pohoršujúce príspevky
|
||||||
mark_as_sensitive_html: Označ médiá pohoršujúcich príspevkov za chúlostivé
|
mark_as_sensitive_html: Označ médiá pohoršujúcich príspevkov za chúlostivé
|
||||||
|
|
|
@ -3,6 +3,18 @@
|
||||||
require 'sidekiq_unique_jobs/web'
|
require 'sidekiq_unique_jobs/web'
|
||||||
require 'sidekiq-scheduler/web'
|
require 'sidekiq-scheduler/web'
|
||||||
|
|
||||||
|
class RedirectWithVary < ActionDispatch::Routing::PathRedirect
|
||||||
|
def build_response(req)
|
||||||
|
super.tap do |response|
|
||||||
|
response.headers['Vary'] = 'Origin, Accept'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def redirect_with_vary(path)
|
||||||
|
RedirectWithVary.new(301, path)
|
||||||
|
end
|
||||||
|
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
# Paths of routes on the web app that to not require to be indexed or
|
# Paths of routes on the web app that to not require to be indexed or
|
||||||
# have alternative format representations requiring separate controllers
|
# have alternative format representations requiring separate controllers
|
||||||
|
@ -97,10 +109,13 @@ Rails.application.routes.draw do
|
||||||
confirmations: 'auth/confirmations',
|
confirmations: 'auth/confirmations',
|
||||||
}
|
}
|
||||||
|
|
||||||
get '/users/:username', to: redirect('/@%{username}'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
# rubocop:disable Style/FormatStringToken - those do not go through the usual formatting functions and are not safe to correct
|
||||||
get '/users/:username/following', to: redirect('/@%{username}/following'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
get '/users/:username', to: redirect_with_vary('/@%{username}'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
||||||
get '/users/:username/followers', to: redirect('/@%{username}/followers'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
get '/users/:username/following', to: redirect_with_vary('/@%{username}/following'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
||||||
get '/users/:username/statuses/:id', to: redirect('/@%{username}/%{id}'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
get '/users/:username/followers', to: redirect_with_vary('/@%{username}/followers'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
||||||
|
get '/users/:username/statuses/:id', to: redirect_with_vary('/@%{username}/%{id}'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
||||||
|
# rubocop:enable Style/FormatStringToken
|
||||||
|
|
||||||
get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" }
|
get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" }
|
||||||
|
|
||||||
resources :accounts, path: 'users', only: [:show], param: :username do
|
resources :accounts, path: 'users', only: [:show], param: :username do
|
||||||
|
|
|
@ -4,7 +4,7 @@ const { createHash } = require('crypto');
|
||||||
const { readFileSync } = require('fs');
|
const { readFileSync } = require('fs');
|
||||||
const { resolve } = require('path');
|
const { resolve } = require('path');
|
||||||
|
|
||||||
const CompressionPlugin = require('@renchap/compression-webpack-plugin');
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||||
const { merge } = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
|
|
|
@ -16,6 +16,8 @@ const config = {
|
||||||
'!app/javascript/mastodon/service_worker/entry.js',
|
'!app/javascript/mastodon/service_worker/entry.js',
|
||||||
'!app/javascript/mastodon/test_setup.js',
|
'!app/javascript/mastodon/test_setup.js',
|
||||||
],
|
],
|
||||||
|
// Those packages are ESM, so we need them to be processed by Babel
|
||||||
|
transformIgnorePatterns: ['/node_modules/(?!(redent|strip-indent)/)'],
|
||||||
coverageDirectory: '<rootDir>/coverage',
|
coverageDirectory: '<rootDir>/coverage',
|
||||||
moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/app/javascript'],
|
moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/app/javascript'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
|
|
34
lib/tasks/webpacker.rake
Normal file
34
lib/tasks/webpacker.rake
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Disable this task as we use pnpm
|
||||||
|
|
||||||
|
require 'semantic_range'
|
||||||
|
|
||||||
|
Rake::Task['webpacker:check_yarn'].clear
|
||||||
|
|
||||||
|
namespace :webpacker do
|
||||||
|
desc 'Verifies if Yarn is installed'
|
||||||
|
task check_yarn: :environment do
|
||||||
|
begin
|
||||||
|
yarn_version = `yarn --version`.strip
|
||||||
|
raise Errno::ENOENT if yarn_version.blank?
|
||||||
|
|
||||||
|
yarn_range = '>=4 <5'
|
||||||
|
is_valid = begin
|
||||||
|
SemanticRange.satisfies?(yarn_version, yarn_range)
|
||||||
|
rescue
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
unless is_valid
|
||||||
|
warn "Mastodon and Webpacker requires Yarn \"#{yarn_range}\" and you are using #{yarn_version}"
|
||||||
|
warn 'Exiting!'
|
||||||
|
exit!
|
||||||
|
end
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
warn 'Yarn not installed. Please see the Mastodon documentation to install the correct version.'
|
||||||
|
warn 'Exiting!'
|
||||||
|
exit!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
24
package.json
24
package.json
|
@ -2,8 +2,11 @@
|
||||||
"name": "@mastodon/mastodon",
|
"name": "@mastodon/mastodon",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:development": "cross-env RAILS_ENV=development NODE_ENV=development ./bin/webpack",
|
"build:development": "cross-env RAILS_ENV=development NODE_ENV=development ./bin/webpack",
|
||||||
"build:production": "cross-env RAILS_ENV=production NODE_ENV=production ./bin/webpack",
|
"build:production": "cross-env RAILS_ENV=production NODE_ENV=production ./bin/webpack",
|
||||||
|
@ -52,7 +55,6 @@
|
||||||
"@material-symbols/svg-600": "^0.13.1",
|
"@material-symbols/svg-600": "^0.13.1",
|
||||||
"@rails/ujs": "^7.1.1",
|
"@rails/ujs": "^7.1.1",
|
||||||
"@reduxjs/toolkit": "^1.9.5",
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
"@renchap/compression-webpack-plugin": "^6.1.4",
|
|
||||||
"@svgr/webpack": "^5.5.0",
|
"@svgr/webpack": "^5.5.0",
|
||||||
"arrow-key-navigation": "^1.2.0",
|
"arrow-key-navigation": "^1.2.0",
|
||||||
"async-mutex": "^0.4.0",
|
"async-mutex": "^0.4.0",
|
||||||
|
@ -60,7 +62,7 @@
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"babel-loader": "^8.3.0",
|
"babel-loader": "^8.3.0",
|
||||||
"babel-plugin-formatjs": "^10.5.1",
|
"babel-plugin-formatjs": "^10.5.1",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "patch:babel-plugin-lodash@npm%3A3.3.4#~/.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch",
|
||||||
"babel-plugin-preval": "^5.1.0",
|
"babel-plugin-preval": "^5.1.0",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||||
"blurhash": "^2.0.5",
|
"blurhash": "^2.0.5",
|
||||||
|
@ -68,6 +70,7 @@
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"cocoon-js-vanilla": "^1.3.0",
|
"cocoon-js-vanilla": "^1.3.0",
|
||||||
"color-blend": "^4.0.0",
|
"color-blend": "^4.0.0",
|
||||||
|
"compression-webpack-plugin": "patch:compression-webpack-plugin@npm%3A6.1.1#~/.yarn/patches/compression-webpack-plugin-npm-6.1.1-3a2a65987e.patch",
|
||||||
"core-js": "^3.30.2",
|
"core-js": "^3.30.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^5.2.7",
|
"css-loader": "^5.2.7",
|
||||||
|
@ -220,10 +223,20 @@
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "^18.0.26",
|
|
||||||
"kind-of": "^6.0.3",
|
"kind-of": "^6.0.3",
|
||||||
"webpack/terser-webpack-plugin": "^4.2.3"
|
"webpack/terser-webpack-plugin": "^4.2.3"
|
||||||
},
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-router-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bufferutil": "^4.0.7",
|
"bufferutil": "^4.0.7",
|
||||||
"utf-8-validate": "^6.0.3"
|
"utf-8-validate": "^6.0.3"
|
||||||
|
@ -234,5 +247,6 @@
|
||||||
"*.{haml}": "bundle exec haml-lint",
|
"*.{haml}": "bundle exec haml-lint",
|
||||||
"*.{js,jsx,ts,tsx}": "eslint --fix",
|
"*.{js,jsx,ts,tsx}": "eslint --fix",
|
||||||
"*.{css,scss}": "stylelint --fix"
|
"*.{css,scss}": "stylelint --fix"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@4.0.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ describe Api::V1::StreamingController do
|
||||||
context 'with streaming api on different host' do
|
context 'with streaming api on different host' do
|
||||||
before do
|
before do
|
||||||
Rails.configuration.x.streaming_api_base_url = "wss://streaming-#{Rails.configuration.x.web_domain}"
|
Rails.configuration.x.streaming_api_base_url = "wss://streaming-#{Rails.configuration.x.web_domain}"
|
||||||
@streaming_host = URI.parse(Rails.configuration.x.streaming_api_base_url).host
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET #index' do
|
describe 'GET #index' do
|
||||||
|
@ -38,7 +37,13 @@ describe Api::V1::StreamingController do
|
||||||
[:scheme, :path, :query, :fragment].each do |part|
|
[:scheme, :path, :query, :fragment].each do |part|
|
||||||
expect(redirect_to_uri.send(part)).to eq(request_uri.send(part)), "redirect target #{part}"
|
expect(redirect_to_uri.send(part)).to eq(request_uri.send(part)), "redirect target #{part}"
|
||||||
end
|
end
|
||||||
expect(redirect_to_uri.host).to eq(@streaming_host), 'redirect target host'
|
expect(redirect_to_uri.host).to eq(streaming_host), 'redirect target host'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def streaming_host
|
||||||
|
URI.parse(Rails.configuration.x.streaming_api_base_url).host
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
describe 'GET #new' do
|
describe 'GET #new' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :new
|
get :new
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
|
@ -19,7 +19,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :show, params: { confirmation_token: 'foobar' }
|
get :show, params: { confirmation_token: 'foobar' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :show, params: { confirmation_token: 'foobar' }
|
get :show, params: { confirmation_token: 'foobar' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
sign_in(user, scope: :user)
|
sign_in(user, scope: :user)
|
||||||
get :show, params: { confirmation_token: 'foobar' }
|
get :show, params: { confirmation_token: 'foobar' }
|
||||||
end
|
end
|
||||||
|
@ -66,7 +66,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
user.approved = false
|
user.approved = false
|
||||||
user.save!
|
user.save!
|
||||||
sign_in(user, scope: :user)
|
sign_in(user, scope: :user)
|
||||||
|
@ -83,7 +83,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :show, params: { confirmation_token: 'foobar' }
|
get :show, params: { confirmation_token: 'foobar' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe Auth::PasswordsController do
|
||||||
|
|
||||||
describe 'GET #new' do
|
describe 'GET #new' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :new
|
get :new
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
|
@ -18,12 +18,14 @@ describe Auth::PasswordsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
@token = user.send_reset_password_instructions
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with valid reset_password_token' do
|
context 'with valid reset_password_token' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
get :edit, params: { reset_password_token: @token }
|
token = user.send_reset_password_instructions
|
||||||
|
|
||||||
|
get :edit, params: { reset_password_token: token }
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -38,9 +40,9 @@ describe Auth::PasswordsController do
|
||||||
|
|
||||||
describe 'POST #update' do
|
describe 'POST #update' do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
let(:password) { 'reset0password' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@password = 'reset0password'
|
|
||||||
request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,9 +52,9 @@ describe Auth::PasswordsController do
|
||||||
let!(:web_push_subscription) { Fabricate(:web_push_subscription, access_token: access_token) }
|
let!(:web_push_subscription) { Fabricate(:web_push_subscription, access_token: access_token) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@token = user.send_reset_password_instructions
|
token = user.send_reset_password_instructions
|
||||||
|
|
||||||
post :update, params: { user: { password: @password, password_confirmation: @password, reset_password_token: @token } }
|
post :update, params: { user: { password: password, password_confirmation: password, reset_password_token: token } }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'redirect to sign in' do
|
it 'redirect to sign in' do
|
||||||
|
@ -63,7 +65,7 @@ describe Auth::PasswordsController do
|
||||||
this_user = User.find(user.id)
|
this_user = User.find(user.id)
|
||||||
|
|
||||||
expect(this_user).to_not be_nil
|
expect(this_user).to_not be_nil
|
||||||
expect(this_user.valid_password?(@password)).to be true
|
expect(this_user.valid_password?(password)).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'deactivates all sessions' do
|
it 'deactivates all sessions' do
|
||||||
|
@ -81,7 +83,7 @@ describe Auth::PasswordsController do
|
||||||
|
|
||||||
context 'with invalid reset_password_token' do
|
context 'with invalid reset_password_token' do
|
||||||
before do
|
before do
|
||||||
post :update, params: { user: { password: @password, password_confirmation: @password, reset_password_token: 'some_invalid_value' } }
|
post :update, params: { user: { password: password, password_confirmation: password, reset_password_token: 'some_invalid_value' } }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders reset password' do
|
it 'renders reset password' do
|
||||||
|
|
|
@ -378,7 +378,7 @@ RSpec.describe Auth::SessionsController do
|
||||||
|
|
||||||
context 'when using a valid webauthn credential' do
|
context 'when using a valid webauthn credential' do
|
||||||
before do
|
before do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
post :create, params: { user: { credential: fake_credential } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
|
post :create, params: { user: { credential: fake_credential } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,7 @@ describe ExportControllerConcern do
|
||||||
end
|
end
|
||||||
|
|
||||||
def export_data
|
def export_data
|
||||||
@export.account.username
|
'body data value'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ describe ExportControllerConcern do
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
expect(response.media_type).to eq 'text/csv'
|
expect(response.media_type).to eq 'text/csv'
|
||||||
expect(response.headers['Content-Disposition']).to start_with 'attachment; filename="anonymous.csv"'
|
expect(response.headers['Content-Disposition']).to start_with 'attachment; filename="anonymous.csv"'
|
||||||
expect(response.body).to eq user.account.username
|
expect(response.body).to eq 'body data value'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns unauthorized when not signed in' do
|
it 'returns unauthorized when not signed in' do
|
||||||
|
|
|
@ -10,7 +10,7 @@ RSpec.describe HomeController do
|
||||||
|
|
||||||
context 'when not signed in' do
|
context 'when not signed in' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
@request.path = '/'
|
request.path = '/'
|
||||||
expect(subject).to have_http_status(:success)
|
expect(subject).to have_http_status(:success)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -194,7 +194,7 @@ RSpec.describe Settings::ImportsController do
|
||||||
let!(:rows) do
|
let!(:rows) do
|
||||||
[
|
[
|
||||||
{ 'acct' => 'foo@bar' },
|
{ 'acct' => 'foo@bar' },
|
||||||
{ 'acct' => 'user@bar', 'show_reblogs' => false, 'notify' => true, 'languages' => ['fr', 'de'] },
|
{ 'acct' => 'user@bar', 'show_reblogs' => false, 'notify' => true, 'languages' => %w(fr de) },
|
||||||
].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
|
].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
it 'stores the challenge on the session' do
|
it 'stores the challenge on the session' do
|
||||||
get :options
|
get :options
|
||||||
|
|
||||||
expect(@controller.session[:webauthn_challenge]).to be_present
|
expect(controller.session[:webauthn_challenge]).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not change webauthn_id' do
|
it 'does not change webauthn_id' do
|
||||||
|
@ -155,7 +155,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
it 'stores the challenge on the session' do
|
it 'stores the challenge on the session' do
|
||||||
get :options
|
get :options
|
||||||
|
|
||||||
expect(@controller.session[:webauthn_challenge]).to be_present
|
expect(controller.session[:webauthn_challenge]).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets user webauthn_id' do
|
it 'sets user webauthn_id' do
|
||||||
|
@ -218,7 +218,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
|
|
||||||
context 'when creation succeeds' do
|
context 'when creation succeeds' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'adds a new credential to user credentials' do
|
it 'adds a new credential to user credentials' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
@ -234,7 +234,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not change webauthn_id' do
|
it 'does not change webauthn_id' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
@ -244,7 +244,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
|
|
||||||
context 'when the nickname is already used' do
|
context 'when the nickname is already used' do
|
||||||
it 'fails' do
|
it 'fails' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: 'USB Key' }
|
post :create, params: { credential: new_webauthn_credential, nickname: 'USB Key' }
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails' do
|
it 'fails' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
|
||||||
|
@ -277,7 +277,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
context 'when user have not enabled webauthn' do
|
context 'when user have not enabled webauthn' do
|
||||||
context 'when creation succeeds' do
|
context 'when creation succeeds' do
|
||||||
it 'creates a webauthn credential' do
|
it 'creates a webauthn credential' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
|
|
@ -5,9 +5,10 @@ require 'rails_helper'
|
||||||
RSpec.describe StatusesCleanupController do
|
RSpec.describe StatusesCleanupController do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
|
let!(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@user = Fabricate(:user)
|
sign_in user, scope: :user
|
||||||
sign_in @user, scope: :user
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET #show' do
|
describe 'GET #show' do
|
||||||
|
@ -30,9 +31,9 @@ RSpec.describe StatusesCleanupController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'updates the account status cleanup policy' do
|
it 'updates the account status cleanup policy' do
|
||||||
expect(@user.account.statuses_cleanup_policy.enabled).to be true
|
expect(user.account.statuses_cleanup_policy.enabled).to be true
|
||||||
expect(@user.account.statuses_cleanup_policy.keep_direct).to be false
|
expect(user.account.statuses_cleanup_policy.keep_direct).to be false
|
||||||
expect(@user.account.statuses_cleanup_policy.keep_polls).to be true
|
expect(user.account.statuses_cleanup_policy.keep_polls).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'redirects' do
|
it 'redirects' do
|
||||||
|
|
|
@ -8,13 +8,15 @@ RSpec.describe TranslationService::DeepL do
|
||||||
let(:plan) { 'advanced' }
|
let(:plan) { 'advanced' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'https://api.deepl.com/v2/languages?type=source').to_return(
|
%w(api-free.deepl.com api.deepl.com).each do |host|
|
||||||
|
stub_request(:get, "https://#{host}/v2/languages?type=source").to_return(
|
||||||
body: '[{"language":"EN","name":"English"},{"language":"UK","name":"Ukrainian"}]'
|
body: '[{"language":"EN","name":"English"},{"language":"UK","name":"Ukrainian"}]'
|
||||||
)
|
)
|
||||||
stub_request(:get, 'https://api.deepl.com/v2/languages?type=target').to_return(
|
stub_request(:get, "https://#{host}/v2/languages?type=target").to_return(
|
||||||
body: '[{"language":"EN-GB","name":"English (British)"},{"language":"ZH","name":"Chinese"}]'
|
body: '[{"language":"EN-GB","name":"English (British)"},{"language":"ZH","name":"Chinese"}]'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#translate' do
|
describe '#translate' do
|
||||||
it 'returns translation with specified source language' do
|
it 'returns translation with specified source language' do
|
||||||
|
@ -73,28 +75,25 @@ RSpec.describe TranslationService::DeepL do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#request' do
|
describe 'the paid and free plan api hostnames' do
|
||||||
before do
|
before do
|
||||||
stub_request(:any, //)
|
service.languages
|
||||||
# rubocop:disable Lint/EmptyBlock
|
|
||||||
service.send(:request, :get, '/v2/languages') { |res| }
|
|
||||||
# rubocop:enable Lint/EmptyBlock
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses paid plan base URL' do
|
context 'without a plan set' do
|
||||||
expect(a_request(:get, 'https://api.deepl.com/v2/languages')).to have_been_made.once
|
it 'uses paid plan base URL and sends an API key' do
|
||||||
|
expect(a_request(:get, 'https://api.deepl.com/v2/languages?type=source').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
|
||||||
|
expect(a_request(:get, 'https://api.deepl.com/v2/languages?type=target').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with free plan' do
|
context 'with the free plan' do
|
||||||
let(:plan) { 'free' }
|
let(:plan) { 'free' }
|
||||||
|
|
||||||
it 'uses free plan base URL' do
|
it 'uses free plan base URL and sends an API key' do
|
||||||
expect(a_request(:get, 'https://api-free.deepl.com/v2/languages')).to have_been_made.once
|
expect(a_request(:get, 'https://api-free.deepl.com/v2/languages?type=source').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
|
||||||
|
expect(a_request(:get, 'https://api-free.deepl.com/v2/languages?type=target').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sends API key' do
|
|
||||||
expect(a_request(:get, 'https://api.deepl.com/v2/languages').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,17 +4,15 @@ require 'rails_helper'
|
||||||
|
|
||||||
describe AccountFinderConcern do
|
describe AccountFinderConcern do
|
||||||
describe 'local finders' do
|
describe 'local finders' do
|
||||||
before do
|
let!(:account) { Fabricate(:account, username: 'Alice') }
|
||||||
@account = Fabricate(:account, username: 'Alice')
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.find_local' do
|
describe '.find_local' do
|
||||||
it 'returns case-insensitive result' do
|
it 'returns case-insensitive result' do
|
||||||
expect(Account.find_local('alice')).to eq(@account)
|
expect(Account.find_local('alice')).to eq(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns correctly cased result' do
|
it 'returns correctly cased result' do
|
||||||
expect(Account.find_local('Alice')).to eq(@account)
|
expect(Account.find_local('Alice')).to eq(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nil without a match' do
|
it 'returns nil without a match' do
|
||||||
|
@ -36,7 +34,7 @@ describe AccountFinderConcern do
|
||||||
|
|
||||||
describe '.find_local!' do
|
describe '.find_local!' do
|
||||||
it 'returns matching result' do
|
it 'returns matching result' do
|
||||||
expect(Account.find_local!('alice')).to eq(@account)
|
expect(Account.find_local!('alice')).to eq(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises on non-matching result' do
|
it 'raises on non-matching result' do
|
||||||
|
@ -54,17 +52,15 @@ describe AccountFinderConcern do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'remote finders' do
|
describe 'remote finders' do
|
||||||
before do
|
let!(:account) { Fabricate(:account, username: 'Alice', domain: 'mastodon.social') }
|
||||||
@account = Fabricate(:account, username: 'Alice', domain: 'mastodon.social')
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.find_remote' do
|
describe '.find_remote' do
|
||||||
it 'returns exact match result' do
|
it 'returns exact match result' do
|
||||||
expect(Account.find_remote('alice', 'mastodon.social')).to eq(@account)
|
expect(Account.find_remote('alice', 'mastodon.social')).to eq(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns case-insensitive result' do
|
it 'returns case-insensitive result' do
|
||||||
expect(Account.find_remote('ALICE', 'MASTODON.SOCIAL')).to eq(@account)
|
expect(Account.find_remote('ALICE', 'MASTODON.SOCIAL')).to eq(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nil when username does not match' do
|
it 'returns nil when username does not match' do
|
||||||
|
@ -90,7 +86,7 @@ describe AccountFinderConcern do
|
||||||
|
|
||||||
describe '.find_remote!' do
|
describe '.find_remote!' do
|
||||||
it 'returns matching result' do
|
it 'returns matching result' do
|
||||||
expect(Account.find_remote!('alice', 'mastodon.social')).to eq(@account)
|
expect(Account.find_remote!('alice', 'mastodon.social')).to eq(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises on non-matching result' do
|
it 'raises on non-matching result' do
|
||||||
|
|
|
@ -650,38 +650,36 @@ describe AccountInteractions do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'ignoring reblogs from an account' do
|
describe 'ignoring reblogs from an account' do
|
||||||
before do
|
let!(:me) { Fabricate(:account, username: 'Me') }
|
||||||
@me = Fabricate(:account, username: 'Me')
|
let!(:you) { Fabricate(:account, username: 'You') }
|
||||||
@you = Fabricate(:account, username: 'You')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with the reblogs option unspecified' do
|
context 'with the reblogs option unspecified' do
|
||||||
before do
|
before do
|
||||||
@me.follow!(@you)
|
me.follow!(you)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'defaults to showing reblogs' do
|
it 'defaults to showing reblogs' do
|
||||||
expect(@me.muting_reblogs?(@you)).to be(false)
|
expect(me.muting_reblogs?(you)).to be(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the reblogs option set to false' do
|
context 'with the reblogs option set to false' do
|
||||||
before do
|
before do
|
||||||
@me.follow!(@you, reblogs: false)
|
me.follow!(you, reblogs: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does mute reblogs' do
|
it 'does mute reblogs' do
|
||||||
expect(@me.muting_reblogs?(@you)).to be(true)
|
expect(me.muting_reblogs?(you)).to be(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the reblogs option set to true' do
|
context 'with the reblogs option set to true' do
|
||||||
before do
|
before do
|
||||||
@me.follow!(@you, reblogs: true)
|
me.follow!(you, reblogs: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not mute reblogs' do
|
it 'does not mute reblogs' do
|
||||||
expect(@me.muting_reblogs?(@you)).to be(false)
|
expect(me.muting_reblogs?(you)).to be(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -296,7 +296,7 @@ RSpec.describe Form::Import do
|
||||||
|
|
||||||
it_behaves_like 'on successful import', 'following', 'merge', 'following_accounts.csv', [
|
it_behaves_like 'on successful import', 'following', 'merge', 'following_accounts.csv', [
|
||||||
{ 'acct' => 'user@example.com', 'show_reblogs' => true, 'notify' => false, 'languages' => nil },
|
{ 'acct' => 'user@example.com', 'show_reblogs' => true, 'notify' => false, 'languages' => nil },
|
||||||
{ 'acct' => 'user@test.com', 'show_reblogs' => true, 'notify' => true, 'languages' => ['en', 'fr'] },
|
{ 'acct' => 'user@test.com', 'show_reblogs' => true, 'notify' => true, 'languages' => %w(en fr) },
|
||||||
]
|
]
|
||||||
|
|
||||||
it_behaves_like 'on successful import', 'muting', 'merge', 'muted_accounts.csv', [
|
it_behaves_like 'on successful import', 'muting', 'merge', 'muted_accounts.csv', [
|
||||||
|
|
|
@ -209,15 +209,13 @@ RSpec.describe PublicFeed do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'with an account passed in' do
|
describe 'with an account passed in' do
|
||||||
subject { described_class.new(@account).get(20).map(&:id) }
|
subject { described_class.new(account).get(20).map(&:id) }
|
||||||
|
|
||||||
before do
|
let!(:account) { Fabricate(:account) }
|
||||||
@account = Fabricate(:account)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'excludes statuses from accounts blocked by the account' do
|
it 'excludes statuses from accounts blocked by the account' do
|
||||||
blocked = Fabricate(:account)
|
blocked = Fabricate(:account)
|
||||||
@account.block!(blocked)
|
account.block!(blocked)
|
||||||
blocked_status = Fabricate(:status, account: blocked)
|
blocked_status = Fabricate(:status, account: blocked)
|
||||||
|
|
||||||
expect(subject).to_not include(blocked_status.id)
|
expect(subject).to_not include(blocked_status.id)
|
||||||
|
@ -225,7 +223,7 @@ RSpec.describe PublicFeed do
|
||||||
|
|
||||||
it 'excludes statuses from accounts who have blocked the account' do
|
it 'excludes statuses from accounts who have blocked the account' do
|
||||||
blocker = Fabricate(:account)
|
blocker = Fabricate(:account)
|
||||||
blocker.block!(@account)
|
blocker.block!(account)
|
||||||
blocked_status = Fabricate(:status, account: blocker)
|
blocked_status = Fabricate(:status, account: blocker)
|
||||||
|
|
||||||
expect(subject).to_not include(blocked_status.id)
|
expect(subject).to_not include(blocked_status.id)
|
||||||
|
@ -233,7 +231,7 @@ RSpec.describe PublicFeed do
|
||||||
|
|
||||||
it 'excludes statuses from accounts muted by the account' do
|
it 'excludes statuses from accounts muted by the account' do
|
||||||
muted = Fabricate(:account)
|
muted = Fabricate(:account)
|
||||||
@account.mute!(muted)
|
account.mute!(muted)
|
||||||
muted_status = Fabricate(:status, account: muted)
|
muted_status = Fabricate(:status, account: muted)
|
||||||
|
|
||||||
expect(subject).to_not include(muted_status.id)
|
expect(subject).to_not include(muted_status.id)
|
||||||
|
@ -241,7 +239,7 @@ RSpec.describe PublicFeed do
|
||||||
|
|
||||||
it 'excludes statuses from accounts from personally blocked domains' do
|
it 'excludes statuses from accounts from personally blocked domains' do
|
||||||
blocked = Fabricate(:account, domain: 'example.com')
|
blocked = Fabricate(:account, domain: 'example.com')
|
||||||
@account.block_domain!(blocked.domain)
|
account.block_domain!(blocked.domain)
|
||||||
blocked_status = Fabricate(:status, account: blocked)
|
blocked_status = Fabricate(:status, account: blocked)
|
||||||
|
|
||||||
expect(subject).to_not include(blocked_status.id)
|
expect(subject).to_not include(blocked_status.id)
|
||||||
|
@ -249,7 +247,7 @@ RSpec.describe PublicFeed do
|
||||||
|
|
||||||
context 'with language preferences' do
|
context 'with language preferences' do
|
||||||
it 'excludes statuses in languages not allowed by the account user' do
|
it 'excludes statuses in languages not allowed by the account user' do
|
||||||
@account.user.update(chosen_languages: [:en, :es])
|
account.user.update(chosen_languages: [:en, :es])
|
||||||
en_status = Fabricate(:status, language: 'en')
|
en_status = Fabricate(:status, language: 'en')
|
||||||
es_status = Fabricate(:status, language: 'es')
|
es_status = Fabricate(:status, language: 'es')
|
||||||
fr_status = Fabricate(:status, language: 'fr')
|
fr_status = Fabricate(:status, language: 'fr')
|
||||||
|
@ -260,7 +258,7 @@ RSpec.describe PublicFeed do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes all languages when user does not have a setting' do
|
it 'includes all languages when user does not have a setting' do
|
||||||
@account.user.update(chosen_languages: nil)
|
account.user.update(chosen_languages: nil)
|
||||||
|
|
||||||
en_status = Fabricate(:status, language: 'en')
|
en_status = Fabricate(:status, language: 'en')
|
||||||
es_status = Fabricate(:status, language: 'es')
|
es_status = Fabricate(:status, language: 'es')
|
||||||
|
@ -270,7 +268,7 @@ RSpec.describe PublicFeed do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes all languages when account does not have a user' do
|
it 'includes all languages when account does not have a user' do
|
||||||
@account.update(user: nil)
|
account.update(user: nil)
|
||||||
|
|
||||||
en_status = Fabricate(:status, language: 'en')
|
en_status = Fabricate(:status, language: 'en')
|
||||||
es_status = Fabricate(:status, language: 'es')
|
es_status = Fabricate(:status, language: 'es')
|
||||||
|
|
|
@ -11,10 +11,6 @@ if RUN_SYSTEM_SPECS
|
||||||
ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}"
|
ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}"
|
||||||
end
|
end
|
||||||
|
|
||||||
if RUN_SEARCH_SPECS
|
|
||||||
# Include any configuration or setups specific to search tests here
|
|
||||||
end
|
|
||||||
|
|
||||||
require File.expand_path('../config/environment', __dir__)
|
require File.expand_path('../config/environment', __dir__)
|
||||||
|
|
||||||
abort('The Rails environment is running in production mode!') if Rails.env.production?
|
abort('The Rails environment is running in production mode!') if Rails.env.production?
|
||||||
|
@ -35,8 +31,6 @@ Sidekiq.logger = nil
|
||||||
|
|
||||||
# System tests config
|
# System tests config
|
||||||
DatabaseCleaner.strategy = [:deletion]
|
DatabaseCleaner.strategy = [:deletion]
|
||||||
streaming_server_manager = StreamingServerManager.new
|
|
||||||
search_data_manager = SearchDataManager.new
|
|
||||||
|
|
||||||
Devise::Test::ControllerHelpers.module_eval do
|
Devise::Test::ControllerHelpers.module_eval do
|
||||||
alias_method :original_sign_in, :sign_in
|
alias_method :original_sign_in, :sign_in
|
||||||
|
@ -100,26 +94,7 @@ RSpec.configure do |config|
|
||||||
Capybara.current_driver = :rack_test
|
Capybara.current_driver = :rack_test
|
||||||
end
|
end
|
||||||
|
|
||||||
config.before :suite do
|
|
||||||
if RUN_SYSTEM_SPECS
|
|
||||||
Webpacker.compile
|
|
||||||
streaming_server_manager.start(port: STREAMING_PORT)
|
|
||||||
end
|
|
||||||
|
|
||||||
if RUN_SEARCH_SPECS
|
|
||||||
Chewy.strategy(:urgent)
|
|
||||||
search_data_manager.prepare_test_data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
config.after :suite do
|
|
||||||
streaming_server_manager.stop
|
|
||||||
|
|
||||||
search_data_manager.cleanup_test_data if RUN_SEARCH_SPECS
|
|
||||||
end
|
|
||||||
|
|
||||||
config.around :each, type: :system do |example|
|
config.around :each, type: :system do |example|
|
||||||
# driven_by :selenium, using: :chrome, screen_size: [1600, 1200]
|
|
||||||
driven_by :selenium, using: :headless_chrome, screen_size: [1600, 1200]
|
driven_by :selenium, using: :headless_chrome, screen_size: [1600, 1200]
|
||||||
|
|
||||||
# The streaming server needs access to the database
|
# The streaming server needs access to the database
|
||||||
|
@ -136,12 +111,6 @@ RSpec.configure do |config|
|
||||||
self.use_transactional_tests = true
|
self.use_transactional_tests = true
|
||||||
end
|
end
|
||||||
|
|
||||||
config.around :each, type: :search do |example|
|
|
||||||
search_data_manager.populate_indexes
|
|
||||||
example.run
|
|
||||||
search_data_manager.remove_indexes
|
|
||||||
end
|
|
||||||
|
|
||||||
config.before do |example|
|
config.before do |example|
|
||||||
unless example.metadata[:paperclip_processing]
|
unless example.metadata[:paperclip_processing]
|
||||||
allow_any_instance_of(Paperclip::Attachment).to receive(:post_process).and_return(true) # rubocop:disable RSpec/AnyInstance
|
allow_any_instance_of(Paperclip::Attachment).to receive(:post_process).and_return(true) # rubocop:disable RSpec/AnyInstance
|
||||||
|
|
|
@ -119,40 +119,39 @@ module TestEndpoints
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'Caching behavior' do
|
describe 'Caching behavior' do
|
||||||
shared_examples 'cachable response' do
|
shared_examples 'cachable response' do |http_success: false|
|
||||||
it 'does not set cookies' do
|
it 'does not set cookies or set public cache control', :aggregate_failures do
|
||||||
expect(response.cookies).to be_empty
|
expect(response.cookies).to be_empty
|
||||||
end
|
|
||||||
|
|
||||||
it 'sets public cache control' do
|
|
||||||
# expect(response.cache_control[:max_age]&.to_i).to be_positive
|
# expect(response.cache_control[:max_age]&.to_i).to be_positive
|
||||||
expect(response.cache_control[:public]).to be_truthy
|
expect(response.cache_control[:public]).to be_truthy
|
||||||
expect(response.cache_control[:private]).to be_falsy
|
expect(response.cache_control[:private]).to be_falsy
|
||||||
expect(response.cache_control[:no_store]).to be_falsy
|
expect(response.cache_control[:no_store]).to be_falsy
|
||||||
expect(response.cache_control[:no_cache]).to be_falsy
|
expect(response.cache_control[:no_cache]).to be_falsy
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200) if http_success
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'non-cacheable response' do
|
shared_examples 'non-cacheable response' do |http_success: false|
|
||||||
it 'sets private cache control' do
|
it 'sets private cache control' do
|
||||||
expect(response.cache_control[:private]).to be_truthy
|
expect(response.cache_control[:private]).to be_truthy
|
||||||
expect(response.cache_control[:no_store]).to be_truthy
|
expect(response.cache_control[:no_store]).to be_truthy
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200) if http_success
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'non-cacheable error' do
|
shared_examples 'non-cacheable error' do
|
||||||
it 'does not return HTTP success' do
|
it 'does not return HTTP success and does not have cache headers', :aggregate_failures do
|
||||||
expect(response).to_not have_http_status(200)
|
expect(response).to_not have_http_status(200)
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not have cache headers' do
|
|
||||||
expect(response.cache_control[:public]).to be_falsy
|
expect(response.cache_control[:public]).to be_falsy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'language-dependent' do
|
shared_examples 'language-dependent' do
|
||||||
it 'has a Vary on Accept-Language' do
|
it 'has a Vary on Accept-Language' do
|
||||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept-language')
|
expect(response_vary_headers).to include('accept-language')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -180,6 +179,15 @@ describe 'Caching behavior' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when anonymously accessed' do
|
context 'when anonymously accessed' do
|
||||||
|
describe '/users/alice' do
|
||||||
|
it 'redirects with proper cache header', :aggregate_failures do
|
||||||
|
get '/users/alice'
|
||||||
|
|
||||||
|
expect(response).to redirect_to('/@alice')
|
||||||
|
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
|
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
|
||||||
describe endpoint do
|
describe endpoint do
|
||||||
before { get endpoint }
|
before { get endpoint }
|
||||||
|
@ -196,7 +204,7 @@ describe 'Caching behavior' do
|
||||||
it_behaves_like 'cachable response'
|
it_behaves_like 'cachable response'
|
||||||
|
|
||||||
it 'has a Vary on Cookie' do
|
it 'has a Vary on Cookie' do
|
||||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie')
|
expect(response_vary_headers).to include('cookie')
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
||||||
|
@ -210,7 +218,7 @@ describe 'Caching behavior' do
|
||||||
it_behaves_like 'cachable response'
|
it_behaves_like 'cachable response'
|
||||||
|
|
||||||
it 'has a Vary on Authorization' do
|
it 'has a Vary on Authorization' do
|
||||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
|
expect(response_vary_headers).to include('authorization')
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
||||||
|
@ -296,7 +304,7 @@ describe 'Caching behavior' do
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response'
|
||||||
|
|
||||||
it 'has a Vary on Cookie' do
|
it 'has a Vary on Cookie' do
|
||||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie')
|
expect(response_vary_headers).to include('cookie')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -305,11 +313,7 @@ describe 'Caching behavior' do
|
||||||
describe endpoint do
|
describe endpoint do
|
||||||
before { get endpoint }
|
before { get endpoint }
|
||||||
|
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -345,7 +349,7 @@ describe 'Caching behavior' do
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response'
|
||||||
|
|
||||||
it 'has a Vary on Authorization' do
|
it 'has a Vary on Authorization' do
|
||||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
|
expect(response_vary_headers).to include('authorization')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -356,11 +360,7 @@ describe 'Caching behavior' do
|
||||||
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -387,11 +387,7 @@ describe 'Caching behavior' do
|
||||||
context 'when allowed for local users only' do
|
context 'when allowed for local users only' do
|
||||||
let(:show_domain_blocks) { 'users' }
|
let(:show_domain_blocks) { 'users' }
|
||||||
|
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when disabled' do
|
context 'when disabled' do
|
||||||
|
@ -415,11 +411,7 @@ describe 'Caching behavior' do
|
||||||
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'cachable response'
|
it_behaves_like 'cachable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
TestEndpoints::REQUIRE_SIGNATURE.each do |endpoint|
|
TestEndpoints::REQUIRE_SIGNATURE.each do |endpoint|
|
||||||
|
@ -428,11 +420,7 @@ describe 'Caching behavior' do
|
||||||
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -450,11 +438,7 @@ describe 'Caching behavior' do
|
||||||
get '/actor', headers: { 'Accept' => 'application/activity+json' }
|
get '/actor', headers: { 'Accept' => 'application/activity+json' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'cachable response'
|
it_behaves_like 'cachable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||||
|
@ -481,11 +465,7 @@ describe 'Caching behavior' do
|
||||||
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'cachable response'
|
it_behaves_like 'cachable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||||
|
@ -494,11 +474,7 @@ describe 'Caching behavior' do
|
||||||
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -522,11 +498,7 @@ describe 'Caching behavior' do
|
||||||
get '/actor', headers: { 'Accept' => 'application/activity+json' }
|
get '/actor', headers: { 'Accept' => 'application/activity+json' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'cachable response'
|
it_behaves_like 'cachable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||||
|
@ -554,11 +526,7 @@ describe 'Caching behavior' do
|
||||||
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'cachable response'
|
it_behaves_like 'cachable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||||
|
@ -567,11 +535,7 @@ describe 'Caching behavior' do
|
||||||
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -585,11 +549,7 @@ describe 'Caching behavior' do
|
||||||
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'cachable response'
|
it_behaves_like 'cachable response', http_success: true
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||||
|
@ -661,7 +621,7 @@ describe 'Caching behavior' do
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response'
|
||||||
|
|
||||||
it 'has a Vary on Authorization' do
|
it 'has a Vary on Authorization' do
|
||||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
|
expect(response_vary_headers).to include('authorization')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -672,13 +632,15 @@ describe 'Caching behavior' do
|
||||||
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'non-cacheable response'
|
it_behaves_like 'non-cacheable response', http_success: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns HTTP success' do
|
private
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
def response_vary_headers
|
||||||
end
|
response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,71 +20,70 @@ RSpec.describe RemoveStatusService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when removed status is not a reblog' do
|
context 'when removed status is not a reblog' do
|
||||||
|
let!(:status) { PostStatusService.new.call(alice, text: 'Hello @bob@example.com ThisIsASecret') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@status = PostStatusService.new.call(alice, text: 'Hello @bob@example.com ThisIsASecret')
|
FavouriteService.new.call(jeff, status)
|
||||||
FavouriteService.new.call(jeff, @status)
|
Fabricate(:status, account: bill, reblog: status, uri: 'hoge')
|
||||||
Fabricate(:status, account: bill, reblog: @status, uri: 'hoge')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes status from author\'s home feed' do
|
it 'removes status from author\'s home feed' do
|
||||||
subject.call(@status)
|
subject.call(status)
|
||||||
expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(@status.id)
|
expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(status.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes status from local follower\'s home feed' do
|
it 'removes status from local follower\'s home feed' do
|
||||||
subject.call(@status)
|
subject.call(status)
|
||||||
expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(@status.id)
|
expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(status.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sends Delete activity to followers' do
|
it 'sends Delete activity to followers' do
|
||||||
subject.call(@status)
|
subject.call(status)
|
||||||
expect(a_request(:post, 'http://example.com/inbox').with(
|
expect(a_request(:post, 'http://example.com/inbox').with(
|
||||||
body: hash_including({
|
body: hash_including({
|
||||||
'type' => 'Delete',
|
'type' => 'Delete',
|
||||||
'object' => {
|
'object' => {
|
||||||
'type' => 'Tombstone',
|
'type' => 'Tombstone',
|
||||||
'id' => ActivityPub::TagManager.instance.uri_for(@status),
|
'id' => ActivityPub::TagManager.instance.uri_for(status),
|
||||||
'atomUri' => OStatus::TagManager.instance.uri_for(@status),
|
'atomUri' => OStatus::TagManager.instance.uri_for(status),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)).to have_been_made.once
|
)).to have_been_made.once
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sends Delete activity to rebloggers' do
|
it 'sends Delete activity to rebloggers' do
|
||||||
subject.call(@status)
|
subject.call(status)
|
||||||
expect(a_request(:post, 'http://example2.com/inbox').with(
|
expect(a_request(:post, 'http://example2.com/inbox').with(
|
||||||
body: hash_including({
|
body: hash_including({
|
||||||
'type' => 'Delete',
|
'type' => 'Delete',
|
||||||
'object' => {
|
'object' => {
|
||||||
'type' => 'Tombstone',
|
'type' => 'Tombstone',
|
||||||
'id' => ActivityPub::TagManager.instance.uri_for(@status),
|
'id' => ActivityPub::TagManager.instance.uri_for(status),
|
||||||
'atomUri' => OStatus::TagManager.instance.uri_for(@status),
|
'atomUri' => OStatus::TagManager.instance.uri_for(status),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)).to have_been_made.once
|
)).to have_been_made.once
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'remove status from notifications' do
|
it 'remove status from notifications' do
|
||||||
expect { subject.call(@status) }.to change {
|
expect { subject.call(status) }.to change {
|
||||||
Notification.where(activity_type: 'Favourite', from_account: jeff, account: alice).count
|
Notification.where(activity_type: 'Favourite', from_account: jeff, account: alice).count
|
||||||
}.from(1).to(0)
|
}.from(1).to(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when removed status is a private self-reblog' do
|
context 'when removed status is a private self-reblog' do
|
||||||
before do
|
let!(:original_status) { Fabricate(:status, account: alice, text: 'Hello ThisIsASecret', visibility: :private) }
|
||||||
@original_status = Fabricate(:status, account: alice, text: 'Hello ThisIsASecret', visibility: :private)
|
let!(:status) { ReblogService.new.call(alice, original_status) }
|
||||||
@status = ReblogService.new.call(alice, @original_status)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sends Undo activity to followers' do
|
it 'sends Undo activity to followers' do
|
||||||
subject.call(@status)
|
subject.call(status)
|
||||||
expect(a_request(:post, 'http://example.com/inbox').with(
|
expect(a_request(:post, 'http://example.com/inbox').with(
|
||||||
body: hash_including({
|
body: hash_including({
|
||||||
'type' => 'Undo',
|
'type' => 'Undo',
|
||||||
'object' => hash_including({
|
'object' => hash_including({
|
||||||
'type' => 'Announce',
|
'type' => 'Announce',
|
||||||
'object' => ActivityPub::TagManager.instance.uri_for(@original_status),
|
'object' => ActivityPub::TagManager.instance.uri_for(original_status),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)).to have_been_made.once
|
)).to have_been_made.once
|
||||||
|
@ -92,19 +91,17 @@ RSpec.describe RemoveStatusService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when removed status is public self-reblog' do
|
context 'when removed status is public self-reblog' do
|
||||||
before do
|
let!(:original_status) { Fabricate(:status, account: alice, text: 'Hello ThisIsASecret', visibility: :public) }
|
||||||
@original_status = Fabricate(:status, account: alice, text: 'Hello ThisIsASecret', visibility: :public)
|
let!(:status) { ReblogService.new.call(alice, original_status) }
|
||||||
@status = ReblogService.new.call(alice, @original_status)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sends Undo activity to followers' do
|
it 'sends Undo activity to followers' do
|
||||||
subject.call(@status)
|
subject.call(status)
|
||||||
expect(a_request(:post, 'http://example.com/inbox').with(
|
expect(a_request(:post, 'http://example.com/inbox').with(
|
||||||
body: hash_including({
|
body: hash_including({
|
||||||
'type' => 'Undo',
|
'type' => 'Undo',
|
||||||
'object' => hash_including({
|
'object' => hash_including({
|
||||||
'type' => 'Announce',
|
'type' => 'Announce',
|
||||||
'object' => ActivityPub::TagManager.instance.uri_for(@original_status),
|
'object' => ActivityPub::TagManager.instance.uri_for(original_status),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)).to have_been_made.once
|
)).to have_been_made.once
|
||||||
|
|
|
@ -19,17 +19,15 @@ describe SearchService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'with an url query' do
|
describe 'with an url query' do
|
||||||
before do
|
let(:query) { 'http://test.host/query' }
|
||||||
@query = 'http://test.host/query'
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when it does not find anything' do
|
context 'when it does not find anything' do
|
||||||
it 'returns the empty results' do
|
it 'returns the empty results' do
|
||||||
service = instance_double(ResolveURLService, call: nil)
|
service = instance_double(ResolveURLService, call: nil)
|
||||||
allow(ResolveURLService).to receive(:new).and_return(service)
|
allow(ResolveURLService).to receive(:new).and_return(service)
|
||||||
results = subject.call(@query, nil, 10, resolve: true)
|
results = subject.call(query, nil, 10, resolve: true)
|
||||||
|
|
||||||
expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
|
expect(service).to have_received(:call).with(query, on_behalf_of: nil)
|
||||||
expect(results).to eq empty_results
|
expect(results).to eq empty_results
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -40,8 +38,8 @@ describe SearchService, type: :service do
|
||||||
service = instance_double(ResolveURLService, call: account)
|
service = instance_double(ResolveURLService, call: account)
|
||||||
allow(ResolveURLService).to receive(:new).and_return(service)
|
allow(ResolveURLService).to receive(:new).and_return(service)
|
||||||
|
|
||||||
results = subject.call(@query, nil, 10, resolve: true)
|
results = subject.call(query, nil, 10, resolve: true)
|
||||||
expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
|
expect(service).to have_received(:call).with(query, on_behalf_of: nil)
|
||||||
expect(results).to eq empty_results.merge(accounts: [account])
|
expect(results).to eq empty_results.merge(accounts: [account])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -52,8 +50,8 @@ describe SearchService, type: :service do
|
||||||
service = instance_double(ResolveURLService, call: status)
|
service = instance_double(ResolveURLService, call: status)
|
||||||
allow(ResolveURLService).to receive(:new).and_return(service)
|
allow(ResolveURLService).to receive(:new).and_return(service)
|
||||||
|
|
||||||
results = subject.call(@query, nil, 10, resolve: true)
|
results = subject.call(query, nil, 10, resolve: true)
|
||||||
expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
|
expect(service).to have_received(:call).with(query, on_behalf_of: nil)
|
||||||
expect(results).to eq empty_results.merge(statuses: [status])
|
expect(results).to eq empty_results.merge(statuses: [status])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,38 +6,36 @@ describe UnblockDomainService, type: :service do
|
||||||
subject { described_class.new }
|
subject { described_class.new }
|
||||||
|
|
||||||
describe 'call' do
|
describe 'call' do
|
||||||
before do
|
let!(:independently_suspended) { Fabricate(:account, domain: 'example.com', suspended_at: 1.hour.ago) }
|
||||||
@independently_suspended = Fabricate(:account, domain: 'example.com', suspended_at: 1.hour.ago)
|
let!(:independently_silenced) { Fabricate(:account, domain: 'example.com', silenced_at: 1.hour.ago) }
|
||||||
@independently_silenced = Fabricate(:account, domain: 'example.com', silenced_at: 1.hour.ago)
|
let!(:domain_block) { Fabricate(:domain_block, domain: 'example.com') }
|
||||||
@domain_block = Fabricate(:domain_block, domain: 'example.com')
|
let!(:silenced) { Fabricate(:account, domain: 'example.com', silenced_at: domain_block.created_at) }
|
||||||
@silenced = Fabricate(:account, domain: 'example.com', silenced_at: @domain_block.created_at)
|
let!(:suspended) { Fabricate(:account, domain: 'example.com', suspended_at: domain_block.created_at) }
|
||||||
@suspended = Fabricate(:account, domain: 'example.com', suspended_at: @domain_block.created_at)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'unsilences accounts and removes block' do
|
it 'unsilences accounts and removes block' do
|
||||||
@domain_block.update(severity: :silence)
|
domain_block.update(severity: :silence)
|
||||||
|
|
||||||
subject.call(@domain_block)
|
subject.call(domain_block)
|
||||||
expect_deleted_domain_block
|
expect_deleted_domain_block
|
||||||
expect(@silenced.reload.silenced?).to be false
|
expect(silenced.reload.silenced?).to be false
|
||||||
expect(@suspended.reload.suspended?).to be true
|
expect(suspended.reload.suspended?).to be true
|
||||||
expect(@independently_suspended.reload.suspended?).to be true
|
expect(independently_suspended.reload.suspended?).to be true
|
||||||
expect(@independently_silenced.reload.silenced?).to be true
|
expect(independently_silenced.reload.silenced?).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'unsuspends accounts and removes block' do
|
it 'unsuspends accounts and removes block' do
|
||||||
@domain_block.update(severity: :suspend)
|
domain_block.update(severity: :suspend)
|
||||||
|
|
||||||
subject.call(@domain_block)
|
subject.call(domain_block)
|
||||||
expect_deleted_domain_block
|
expect_deleted_domain_block
|
||||||
expect(@suspended.reload.suspended?).to be false
|
expect(suspended.reload.suspended?).to be false
|
||||||
expect(@silenced.reload.silenced?).to be false
|
expect(silenced.reload.silenced?).to be false
|
||||||
expect(@independently_suspended.reload.suspended?).to be true
|
expect(independently_suspended.reload.suspended?).to be true
|
||||||
expect(@independently_silenced.reload.silenced?).to be true
|
expect(independently_silenced.reload.silenced?).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def expect_deleted_domain_block
|
def expect_deleted_domain_block
|
||||||
expect { @domain_block.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
expect { domain_block.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,3 +41,38 @@ class SearchDataManager
|
||||||
Tag.destroy_all
|
Tag.destroy_all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.before :suite do
|
||||||
|
if search_examples_present?
|
||||||
|
# Configure chewy to use `urgent` strategy to index documents
|
||||||
|
Chewy.strategy(:urgent)
|
||||||
|
|
||||||
|
# Create search data
|
||||||
|
search_data_manager.prepare_test_data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
config.after :suite do
|
||||||
|
if search_examples_present?
|
||||||
|
# Clean up after search data
|
||||||
|
search_data_manager.cleanup_test_data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
config.around :each, type: :search do |example|
|
||||||
|
search_data_manager.populate_indexes
|
||||||
|
example.run
|
||||||
|
search_data_manager.remove_indexes
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def search_data_manager
|
||||||
|
@search_data_manager ||= SearchDataManager.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_examples_present?
|
||||||
|
RUN_SEARCH_SPECS
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -76,3 +76,32 @@ class StreamingServerManager
|
||||||
@running_thread.join
|
@running_thread.join
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.before :suite do
|
||||||
|
if streaming_examples_present?
|
||||||
|
# Compile assets
|
||||||
|
Webpacker.compile
|
||||||
|
|
||||||
|
# Start the node streaming server
|
||||||
|
streaming_server_manager.start(port: STREAMING_PORT)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
config.after :suite do
|
||||||
|
if streaming_examples_present?
|
||||||
|
# Stop the node streaming server
|
||||||
|
streaming_server_manager.stop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def streaming_server_manager
|
||||||
|
@streaming_server_manager ||= StreamingServerManager.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def streaming_examples_present?
|
||||||
|
RUN_SYSTEM_SPECS
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe BlacklistedEmailValidator, type: :validator do
|
RSpec.describe BlacklistedEmailValidator do
|
||||||
describe '#validate' do
|
describe '#validate' do
|
||||||
subject { described_class.new.validate(user); errors }
|
subject { described_class.new.validate(user); errors }
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe DisallowedHashtagsValidator, type: :validator do
|
RSpec.describe DisallowedHashtagsValidator do
|
||||||
let(:disallowed_tags) { [] }
|
let(:disallowed_tags) { [] }
|
||||||
|
|
||||||
describe '#validate' do
|
describe '#validate' do
|
||||||
|
|
|
@ -2,48 +2,76 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe FollowLimitValidator, type: :validator do
|
RSpec.describe FollowLimitValidator do
|
||||||
describe '#validate' do
|
describe '#validate' do
|
||||||
|
context 'with a nil account' do
|
||||||
|
it 'does not add validation errors to base' do
|
||||||
|
follow = Fabricate.build(:follow, account: nil)
|
||||||
|
|
||||||
|
follow.valid?
|
||||||
|
|
||||||
|
expect(follow.errors[:base]).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a non-local account' do
|
||||||
|
it 'does not add validation errors to base' do
|
||||||
|
follow = Fabricate.build(:follow, account: Account.new(domain: 'host.example'))
|
||||||
|
|
||||||
|
follow.valid?
|
||||||
|
|
||||||
|
expect(follow.errors[:base]).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a local account' do
|
||||||
|
let(:account) { Account.new }
|
||||||
|
|
||||||
|
context 'when the followers count is under the limit' do
|
||||||
before do
|
before do
|
||||||
allow_any_instance_of(described_class).to receive(:limit_reached?).with(account) do
|
allow(account).to receive(:following_count).and_return(described_class::LIMIT - 100)
|
||||||
limit_reached
|
|
||||||
end
|
end
|
||||||
|
|
||||||
described_class.new.validate(follow)
|
it 'does not add validation errors to base' do
|
||||||
end
|
follow = Fabricate.build(:follow, account: account)
|
||||||
|
|
||||||
let(:follow) { instance_double(Follow, account: account, errors: errors) }
|
follow.valid?
|
||||||
let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
|
|
||||||
let(:account) { instance_double(Account, nil?: _nil, local?: local, following_count: 0, followers_count: 0) }
|
|
||||||
let(:_nil) { true }
|
|
||||||
let(:local) { false }
|
|
||||||
|
|
||||||
context 'with follow.account.nil? || !follow.account.local?' do
|
expect(follow.errors[:base]).to be_empty
|
||||||
let(:_nil) { true }
|
|
||||||
|
|
||||||
it 'not calls errors.add' do
|
|
||||||
expect(errors).to_not have_received(:add).with(:base, any_args)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with !(follow.account.nil? || !follow.account.local?)' do
|
context 'when the following count is over the limit' do
|
||||||
let(:_nil) { false }
|
before do
|
||||||
let(:local) { true }
|
allow(account).to receive(:following_count).and_return(described_class::LIMIT + 100)
|
||||||
|
end
|
||||||
|
|
||||||
context 'when limit_reached?' do
|
context 'when the followers count is low' do
|
||||||
let(:limit_reached) { true }
|
before do
|
||||||
|
allow(account).to receive(:followers_count).and_return(10)
|
||||||
|
end
|
||||||
|
|
||||||
it 'calls errors.add' do
|
it 'adds validation errors to base' do
|
||||||
expect(errors).to have_received(:add)
|
follow = Fabricate.build(:follow, account: account)
|
||||||
.with(:base, I18n.t('users.follow_limit_reached', limit: FollowLimitValidator::LIMIT))
|
|
||||||
|
follow.valid?
|
||||||
|
|
||||||
|
expect(follow.errors[:base]).to include(I18n.t('users.follow_limit_reached', limit: FollowLimitValidator::LIMIT))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with !limit_reached?' do
|
context 'when the followers count is high' do
|
||||||
let(:limit_reached) { false }
|
before do
|
||||||
|
allow(account).to receive(:followers_count).and_return(100_000)
|
||||||
|
end
|
||||||
|
|
||||||
it 'not calls errors.add' do
|
it 'does not add validation errors to base' do
|
||||||
expect(errors).to_not have_received(:add).with(:base, any_args)
|
follow = Fabricate.build(:follow, account: account)
|
||||||
|
|
||||||
|
follow.valid?
|
||||||
|
|
||||||
|
expect(follow.errors[:base]).to be_empty
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe PollValidator, type: :validator do
|
RSpec.describe PollValidator do
|
||||||
describe '#validate' do
|
describe '#validate' do
|
||||||
before do
|
before do
|
||||||
validator.validate(poll)
|
validator.validate(poll)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe StatusPinValidator, type: :validator do
|
RSpec.describe StatusPinValidator do
|
||||||
describe '#validate' do
|
describe '#validate' do
|
||||||
before do
|
before do
|
||||||
subject.validate(pin)
|
subject.validate(pin)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue