diff --git a/docker-compose.yml b/docker-compose.yml index 1c98169d242..c7c9ad81806 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -123,6 +123,13 @@ services: networks: - testing + hocuspocus-test: + image: openproject/hocuspocus:latest + environment: + SECRET: secret12345 + networks: + - testing + frontend-test: build: <<: *frontend-build @@ -157,8 +164,11 @@ services: condition: service_healthy cuprite-chrome: condition: service_started + hocuspocus-test: + condition: service_started environment: RAILS_ENV: test + OPENPROJECT_TESTING_WITH_DOCKER: "true" OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}" DATABASE_URL: postgresql://openproject:openproject@db-test/openproject DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL: "true" diff --git a/modules/documents/spec/features/real_time_collaboration_spec.rb b/modules/documents/spec/features/real_time_collaboration_spec.rb new file mode 100644 index 00000000000..e75b1706271 --- /dev/null +++ b/modules/documents/spec/features/real_time_collaboration_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require_module_spec_helper + +RSpec.describe "Real-time collaboration with Hocuspocus for documents", + :js, + :selenium do + let(:editor) { FormFields::Primerized::BlockNoteEditorInput.new } + let(:admin) { create(:admin, firstname: "Armin", lastname: "Admin") } + let(:member_role) { create(:existing_project_role, permissions: [:view_documents]) } + let(:readonly_user) { create(:user, member_with_roles: { project => member_role }, firstname: "Rita", lastname: "Readonly") } + let(:project) { create(:project) } + let(:document) { create(:document, :collaborative, project:) } + + before do + # These tests require a running Hocuspocus server. You can either run one + # locally or use the docker test setup. + host = ENV["OPENPROJECT_TESTING_WITH_DOCKER"] == "true" ? "hocuspocus-test" : "127.0.0.1" + port = 1234 + Setting.collaborative_editing_hocuspocus_url = "ws://#{host}:#{port}" + Setting.collaborative_editing_hocuspocus_secret = "secret12345" + + begin + TCPSocket.new(host, port).close + rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e + raise("Hocuspocus server not reachable at #{host}:#{port} - #{e.message}") + end + end + + context "with write permission" do + before do + login_as(admin) + end + + it "shows the editor" do + visit document_path(document) + expect(page).to have_test_selector("blocknote-document-description") + expect(page).to have_no_content("Armin Admin") + page.find_link("1 active editor").click + expect(page).to have_content("Armin Admin") + end + + it "renders a collaborative document and saves changes to the database" do + visit document_path(document) + expect(document.content_binary).to be_nil + expect(page).to have_test_selector("blocknote-document-description") + + editor.fill_in("Hello Hocuspocus") + wait_for { document.reload.content_binary }.to be_present + expect(document.description).to eq("Hello Hocuspocus\n") + end + end + + context "with readonly permission" do + before do + login_as(readonly_user) + end + + it "shows the editor but does not accept writes" do + # rubocop:disable Layout/LineLength + binary = "ARL108iNDAAHAQ5kb2N1bWVudC1zdG9yZQMKYmxvY2tHcm91cAcA9dPIjQwAAw5ibG9ja0NvbnRhaW5lcgcA9dPIjQwBAwlwYXJhZ3JhcGgHAPXTyI0MAgYEAPXTyI0MAwFIKAD108iNDAIPYmFja2dyb3VuZENvbG9yAXcHZGVmYXVsdCgA9dPIjQwCCXRleHRDb2xvcgF3B2RlZmF1bHQoAPXTyI0MAg10ZXh0QWxpZ25tZW50AXcEbGVmdCgA9dPIjQwBAmlkAXcOaW5pdGlhbEJsb2NrSWSH9dPIjQwBAw5ibG9ja0NvbnRhaW5lcgcA9dPIjQwJAwlwYXJhZ3JhcGgoAPXTyI0MCg9iYWNrZ3JvdW5kQ29sb3IBdwdkZWZhdWx0KAD108iNDAoJdGV4dENvbG9yAXcHZGVmYXVsdCgA9dPIjQwKDXRleHRBbGlnbm1lbnQBdwRsZWZ0KAD108iNDAkCaWQBdyQ4OTc0Yzk0YS1kZWZiLTRmMjEtYTc4Yi1mN2MyZTg5ZjUxZmKE9dPIjQwEBGVsbG+B9dPIjQwSAYT108iNDBMLIEhvY3VzcG9jdXMB9dPIjQwBEwE=" + # rubocop:enable Layout/LineLength + document.update!(content_binary: binary, description: "Hello Hocuspocus\n") + visit document_path(document) + expect(page).to have_test_selector("blocknote-document-description") + wait_for { editor.content }.to have_content("Hello Hocuspocus") + editor.fill_in("Nothing changes") + # Hocuspocus takes some time before saving. It's hard to wait for + # something to not change, so this sleeps, although it's bad to do that in tests. + sleep 5 # rubocop:disable OpenProject/NoSleepInFeatureSpecs + expect(document.reload.content_binary).to eql(binary) # nothing changed + expect(document.description).to eq("Hello Hocuspocus\n") + end + end +end