From 552de31af85315f3d5cc90c617c1c774dac5eb77 Mon Sep 17 00:00:00 2001 From: Kevin Mulhern Date: Fri, 30 Jun 2023 11:44:25 +0100 Subject: [PATCH] Feature: Add Project Submissions With Hotwire (#3852) Because: * We are converting project submissions from react to Hotwire. This commit: * Add Hotwire modal * Display project submissions form partial when within the modal when adding a project * Redesign the project submission form, modern styling and clearer privacy controls. * Prepend users and hide the add submission button with a turbo stream https://github.com/TheOdinProject/theodinproject/assets/7963776/592c6dcd-a426-4e24-b810-970715df4254 --- app/assets/images/icons/link.svg | 3 + app/builders/tailwind_form_builder.rb | 18 ++++-- app/components/application_component.rb | 1 + app/components/modal_component.html.erb | 54 ++++++++++++++++ app/components/modal_component.rb | 9 +++ .../project_submissions/item_component.rb | 4 ++ .../v2_project_submissions_controller.rb | 31 +++++++-- .../controllers/modal_controller.js | 34 ++++++++++ app/javascript/stylesheets/buttons.css | 8 ++- app/models/project_submission.rb | 2 +- app/views/layouts/application.html.erb | 1 + .../lessons/_project_submissions.html.erb | 4 +- .../v2_project_submissions/_form.html.erb | 52 +++++++++++++++ .../create.turbo_stream.erb | 5 ++ .../v2_project_submissions/index.html.erb | 10 ++- .../v2_project_submissions/new.html.erb | 3 + config/routes.rb | 2 +- config/utility_classes.yml | 4 +- .../tailwind_form_builder/email_field.snap | 2 +- .../tailwind_form_builder/password_field.snap | 2 +- .../tailwind_form_builder/text_area.snap | 2 +- .../tailwind_form_builder/text_field.snap | 2 +- .../text_field_with_class.snap | 2 +- .../text_field_with_leading_icon.snap | 1 + spec/builders/tailwind_form_builder_spec.rb | 7 ++ spec/models/project_submission_spec.rb | 6 +- .../support/pages/project_submissions/form.rb | 5 ++ .../add_submission_spec.rb | 64 +++++++++++++++++++ 28 files changed, 314 insertions(+), 24 deletions(-) create mode 100644 app/assets/images/icons/link.svg create mode 100644 app/components/modal_component.html.erb create mode 100644 app/components/modal_component.rb create mode 100644 app/javascript/controllers/modal_controller.js create mode 100644 app/views/lessons/v2_project_submissions/_form.html.erb create mode 100644 app/views/lessons/v2_project_submissions/create.turbo_stream.erb create mode 100644 app/views/lessons/v2_project_submissions/new.html.erb create mode 100644 spec/builders/__snapshots__/tailwind_form_builder/text_field_with_leading_icon.snap create mode 100644 spec/system/v2_lesson_project_submissions/add_submission_spec.rb diff --git a/app/assets/images/icons/link.svg b/app/assets/images/icons/link.svg new file mode 100644 index 0000000000..0fec094591 --- /dev/null +++ b/app/assets/images/icons/link.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/builders/tailwind_form_builder.rb b/app/builders/tailwind_form_builder.rb index b368fcd9ae..2f2be99323 100644 --- a/app/builders/tailwind_form_builder.rb +++ b/app/builders/tailwind_form_builder.rb @@ -1,8 +1,14 @@ class TailwindFormBuilder < ActionView::Helpers::FormBuilder - def text_field(attribute, options = {}) - default_opts = { class: classes_for(attribute, options) } + def text_field(attribute, options = {}, &) + if options[:leading_icon] + default_opts = { class: "#{classes_for(attribute, options)} pl-10" } - text_layout(attribute) { super(attribute, options.merge(default_opts)) } + attribute_error_message(attribute) + text_layout(attribute) { leading_icon(&) + super(attribute, options.merge(default_opts)) } + else + default_opts = { class: classes_for(attribute, options) } + + text_layout(attribute) { super(attribute, options.merge(default_opts)) } + end + attribute_error_message(attribute) end def email_field(attribute, options = {}) @@ -44,7 +50,7 @@ def classes_for(attribute, options) end def text_layout(attribute) - @template.content_tag :div, class: 'mt-1 relative rounded-md shadow-sm' do + @template.content_tag :div, class: 'mt-2 relative rounded-md shadow-sm' do yield + attribute_error_icon(attribute) end end @@ -63,6 +69,10 @@ def attribute_error_icon(attribute) end end + def leading_icon(&) + @template.content_tag(:div, class: 'pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3', &) + end + def attribute_error_message(attribute) state = @object.errors[attribute].present? ? :visible : :hidden diff --git a/app/components/application_component.rb b/app/components/application_component.rb index 317c23b290..00578e1952 100644 --- a/app/components/application_component.rb +++ b/app/components/application_component.rb @@ -1,5 +1,6 @@ class ApplicationComponent < ViewComponent::Base include Classy::Yaml::ComponentHelpers + include Turbo::FramesHelper private diff --git a/app/components/modal_component.html.erb b/app/components/modal_component.html.erb new file mode 100644 index 0000000000..9ddbe2ced0 --- /dev/null +++ b/app/components/modal_component.html.erb @@ -0,0 +1,54 @@ +<%= turbo_frame_tag 'modal' do %> + +<% end %> diff --git a/app/components/modal_component.rb b/app/components/modal_component.rb new file mode 100644 index 0000000000..46f0455020 --- /dev/null +++ b/app/components/modal_component.rb @@ -0,0 +1,9 @@ +class ModalComponent < ApplicationComponent + def initialize(title:) + @title = title + end + + private + + attr_reader :title +end diff --git a/app/components/project_submissions/item_component.rb b/app/components/project_submissions/item_component.rb index 0257b57a97..9f9ea84476 100644 --- a/app/components/project_submissions/item_component.rb +++ b/app/components/project_submissions/item_component.rb @@ -4,6 +4,10 @@ def initialize(item:) @item = item end + def render? + item.present? + end + private attr_reader :item diff --git a/app/controllers/lessons/v2_project_submissions_controller.rb b/app/controllers/lessons/v2_project_submissions_controller.rb index de40e1f427..e619885295 100644 --- a/app/controllers/lessons/v2_project_submissions_controller.rb +++ b/app/controllers/lessons/v2_project_submissions_controller.rb @@ -4,15 +4,29 @@ class V2ProjectSubmissionsController < ApplicationController before_action :set_lesson def index - @pagy, @project_submissions = pagy(public_project_submissions, items: params.fetch(:limit, 15)) + @current_user_submission = current_user.project_submissions.find_by(lesson: @lesson) + @pagy, @project_submissions = pagy(project_submissions_query.public_submissions, items: params.fetch(:limit, 15)) end - private + def new + @project_submission = current_user.project_submissions.new(lesson: @lesson) + end - def public_project_submissions - project_submissions_query.public_submissions + def create + @project_submission = current_user.project_submissions.new(project_submission_params.merge(lesson: @lesson)) + + respond_to do |format| + if @project_submission.save + format.html { redirect_to lesson_path(@lesson), notice: 'Project submitted' } + format.turbo_stream + else + format.html { render :new, status: :unprocessable_entity } + end + end end + private + def project_submissions_query @project_submissions_query ||= ::LessonProjectSubmissionsQuery.new( lesson: @lesson, @@ -23,5 +37,14 @@ def project_submissions_query def set_lesson @lesson = Lesson.find(params[:lesson_id]) end + + def project_submission_params + params.require(:project_submission).permit( + :repo_url, + :live_preview_url, + :is_public, + :lesson_id + ) + end end end diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js new file mode 100644 index 0000000000..dcf511ebd0 --- /dev/null +++ b/app/javascript/controllers/modal_controller.js @@ -0,0 +1,34 @@ +/* eslint-disable class-methods-use-this */ +import { Controller } from '@hotwired/stimulus'; +import { enter, leave } from 'el-transition'; + +export default class ModalController extends Controller { + static targets = ['transitionable']; + + connect() { + this.lockScroll(); + this.transitionableTargets.forEach((element) => enter(element)); + } + + close() { + Promise.all(this.transitionableTargets.map((element) => leave(element))).then(() => { + this.element.parentElement.removeAttribute('src'); + this.element.remove(); + this.unlockScroll(); + }); + } + + submitEnd(event) { + if (event.detail.success) { + this.close(); + } + } + + lockScroll() { + document.body.classList.add('overflow-hidden', 'pr-4'); + } + + unlockScroll() { + document.body.classList.remove('overflow-hidden', 'pr-4'); + } +} diff --git a/app/javascript/stylesheets/buttons.css b/app/javascript/stylesheets/buttons.css index bc3cba6d28..1cc93db2de 100644 --- a/app/javascript/stylesheets/buttons.css +++ b/app/javascript/stylesheets/buttons.css @@ -2,7 +2,7 @@ .button { min-width: 100px; @apply inline-flex items-center px-6 justify-center py-3 border border-transparent - rounded-md shadow-sm text-white focus:outline-none + rounded-md shadow-sm text-white focus:outline-none focus:ring-2 focus:ring-offset-1 hover:text-white focus:text-white no-underline cursor-pointer dark:focus:ring-offset-black text-sm } @@ -28,6 +28,12 @@ dark:bg-gray-300 dark:hover:bg-gray-400 focus:text-gray-700 px-4 py-2 dark:hover:text-gray-900 } + .button--white { + @apply bg-white rounded-md px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset + ring-gray-300 hover:bg-gray-50 hover:text-gray-900 + dark:bg-white/10 dark:text-white dark:hover:bg-white/20 dark:ring-transparent; + } + .button--clear { @apply border-gray-300 text-gray-700 hover:text-gray-700 bg-white hover:bg-gray-50 focus:ring-gray-400 focus:text-gray-700 dark:text-gray-300 dark:hover:bg-gray-700 diff --git a/app/models/project_submission.rb b/app/models/project_submission.rb index 237a2ccaba..53326c8793 100644 --- a/app/models/project_submission.rb +++ b/app/models/project_submission.rb @@ -15,7 +15,7 @@ class ProjectSubmission < ApplicationRecord validates :live_preview_url, url: true, allow_blank: true validate :live_preview_allowed validates :repo_url, presence: { message: 'Required' } - validates :user_id, uniqueness: { scope: :lesson_id } + validates :user_id, uniqueness: { scope: :lesson_id, message: 'You have already submitted a project for this lesson' } scope :only_public, -> { where(is_public: true) } scope :not_removed_by_admin, -> { where(discarded_at: nil) } diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index c34362019d..c1544f0735 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -36,6 +36,7 @@ <%= render 'shared/navbar' %> <%= render 'shared/flash', flash: flash if flash.any? %> + <%= turbo_frame_tag 'modal' %> <%= yield %> <%= render 'shared/footer' %> diff --git a/app/views/lessons/_project_submissions.html.erb b/app/views/lessons/_project_submissions.html.erb index ccce23dbbd..0e0b988a0c 100644 --- a/app/views/lessons/_project_submissions.html.erb +++ b/app/views/lessons/_project_submissions.html.erb @@ -1,6 +1,6 @@ <% if Feature.enabled?(:v2_project_submissions, current_user) %> - <%= turbo_frame_tag 'submissions-list', src: lesson_v2_project_submissions_path(lesson, limit: 10) do %> -
+ <%= turbo_frame_tag dom_id(@lesson, 'project-submissions'), src: lesson_v2_project_submissions_path(lesson, limit: 10) do %> +
<%= inline_svg_tag 'icons/spinner.svg', class: 'animate-spin h-12 w-12 text-gray-400 dark:text-gray-300', aria: true, title: 'dismiss', desc: 'dismiss icon' %>
<% end %> diff --git a/app/views/lessons/v2_project_submissions/_form.html.erb b/app/views/lessons/v2_project_submissions/_form.html.erb new file mode 100644 index 0000000000..123e7b677f --- /dev/null +++ b/app/views/lessons/v2_project_submissions/_form.html.erb @@ -0,0 +1,52 @@ +<%= turbo_frame_tag 'project_submission_form', class: 'w-full' do %> + <%= form_with url: lesson_v2_project_submissions_path(lesson.id), model: project_submission, builder: TailwindFormBuilder do |form| %> +
+
+
+ <%= form.label :repo_url, 'GitHub repository' %> + <%= form.text_field :repo_url, leading_icon: true, autofocus: true, class: 'text-sm', placeholder: 'http://github.com/user-name/repo-name', data: { test_id: 'repo-url-field' } do %> + <%= inline_svg_tag 'socials/github.svg', class: 'h-5 w-5 text-gray-400', aria: true, title: 'Project GitHub repository', desc: 'GitHub icon' %> + <% end %> +
+ +
+ <%= form.label :live_preview_url, 'Live preview (optional)' %> + <%= form.text_field :live_preview_url, leading_icon: true, placeholder: 'http://example.com', class: 'text-sm', data: { test_id: 'live-preview-url-field' } do %> + <%= inline_svg_tag 'icons/link.svg', class: 'h-5 w-5 text-gray-400', aria: true, title: 'Project live preview', desc: 'Link icon' %> + <% end %> +
+ +
+ Privacy +
+
+
+ <%= form.radio_button :is_public, true, class: 'h-4 w-4 border-gray-300 dark:border-gray-500 dark:bg-gray-700/50 dark:checked:bg-gold-600 dark:checked:border-gold-600 dark:focus:ring-offset-gray-800 text-gold-600 focus:ring-gold-600' %> +
+
+ <%= form.label :is_public_true, 'Public to other learners', class: 'dark:text-gray-200' %> +

Anyone can see this project in the submissions list.

+
+
+
+
+
+ <%= form.radio_button :is_public, false, class: 'h-4 w-4 border-gray-300 dark:border-gray-500 dark:bg-gray-700/50 dark:checked:bg-gold-600 dark:checked:border-gold-600 dark:focus:ring-offset-gray-800 text-gold-600 focus:ring-gold-600' %> +
+
+ <%= form.label :is_public_false, 'Private to you' %> +

Only you can see this project in the submissions list.

+
+
+
+
+
+
+ +
+ <%= form.submit 'Save', class: 'cursor-pointer inline-flex w-full justify-center rounded-md button--primary px-5 py-2 text-sm font-semibold text-white shadow-sm sm:ml-3 sm:w-auto', data: { test_id: 'submit-btn' } %> + +
+
+ <% end %> +<% end %> diff --git a/app/views/lessons/v2_project_submissions/create.turbo_stream.erb b/app/views/lessons/v2_project_submissions/create.turbo_stream.erb new file mode 100644 index 0000000000..51640738e2 --- /dev/null +++ b/app/views/lessons/v2_project_submissions/create.turbo_stream.erb @@ -0,0 +1,5 @@ +<%= turbo_stream.prepend "submissions-list" do %> + <%= render ProjectSubmissions::ItemComponent.new(item: @project_submission) %> +<% end %> + +<%= turbo_stream.remove "add-submission-button" %> diff --git a/app/views/lessons/v2_project_submissions/index.html.erb b/app/views/lessons/v2_project_submissions/index.html.erb index b4a3471ea3..1d51ab4173 100644 --- a/app/views/lessons/v2_project_submissions/index.html.erb +++ b/app/views/lessons/v2_project_submissions/index.html.erb @@ -8,7 +8,7 @@ <% end %>
-<%= turbo_frame_tag 'submissions-list' do %> +<%= turbo_frame_tag dom_id(@lesson, 'project-submissions') do %>
@@ -18,11 +18,15 @@ <%= @lesson.course.title %> : (<%= @lesson.title %>)
+ <% if @current_user_submission.nil? %> + <%= link_to 'Add solution', new_lesson_v2_project_submission_path(@lesson), id: 'add-submission-button', class: 'button button--primary', data: { turbo_frame: 'modal', test_id: 'add_submission_btn' } %> + <% end %> -
+ <%= turbo_frame_tag 'submissions-list', data: { test_id: 'submissions-list' } do %> + <%= render ProjectSubmissions::ItemComponent.new(item: @current_user_submission) %> <%= render ProjectSubmissions::ItemComponent.with_collection(@project_submissions) %> -
+ <% end %> <% end %> diff --git a/app/views/lessons/v2_project_submissions/new.html.erb b/app/views/lessons/v2_project_submissions/new.html.erb new file mode 100644 index 0000000000..ed4435641c --- /dev/null +++ b/app/views/lessons/v2_project_submissions/new.html.erb @@ -0,0 +1,3 @@ +<%= render ModalComponent.new(title: 'Submit your project') do %> + <%= render 'lessons/v2_project_submissions/form', lesson: @lesson, project_submission: @project_submission %> +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 059d5e5df4..c1f846e5f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -90,7 +90,7 @@ resources :lessons, only: :show do resources :project_submissions, only: %i[index], controller: 'lessons/project_submissions' - resources :v2_project_submissions, only: %i[index], controller: 'lessons/v2_project_submissions' + resources :v2_project_submissions, only: %i[index new create], controller: 'lessons/v2_project_submissions' resource :completion, only: %i[create destroy], controller: 'lessons/completions' end diff --git a/config/utility_classes.yml b/config/utility_classes.yml index 51bd63b3c3..76454d6156 100644 --- a/config/utility_classes.yml +++ b/config/utility_classes.yml @@ -1,6 +1,6 @@ text_field: - base: "block w-full border rounded-md py-2 px-3 focus:outline-none dark:bg-gray-700/50 dark:border-gray-500 dark:text-gray-300 dark:placeholder-gray-400 dark:focus:ring-gray-500 dark:focus:ring-2 dark:focus:border-transparent" - valid: "border-gray-300 focus:ring-blue-600 focus:border-blue-600" + base: "block w-full border rounded-md py-2 px-3 focus:outline-none dark:bg-gray-700/50 dark:border-gray-500 dark:text-gray-300 dark:placeholder-gray-400 dark:focus:ring-2 dark:focus:border-transparent" + valid: "border-gray-300 focus:ring-blue-600 focus:border-blue-600 dark:focus:ring-blue-400" invalid: "pr-10 border-red-300 text-red-900 placeholder-red-300 focus:ring-red-500 focus:border-red-500" label: base: "block text-sm font-medium text-gray-700 dark:text-gray-200" diff --git a/spec/builders/__snapshots__/tailwind_form_builder/email_field.snap b/spec/builders/__snapshots__/tailwind_form_builder/email_field.snap index eda6976201..4b61411177 100644 --- a/spec/builders/__snapshots__/tailwind_form_builder/email_field.snap +++ b/spec/builders/__snapshots__/tailwind_form_builder/email_field.snap @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/spec/builders/__snapshots__/tailwind_form_builder/password_field.snap b/spec/builders/__snapshots__/tailwind_form_builder/password_field.snap index 230b1e4980..266a57fab0 100644 --- a/spec/builders/__snapshots__/tailwind_form_builder/password_field.snap +++ b/spec/builders/__snapshots__/tailwind_form_builder/password_field.snap @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/spec/builders/__snapshots__/tailwind_form_builder/text_area.snap b/spec/builders/__snapshots__/tailwind_form_builder/text_area.snap index 7e40061434..3313971adf 100644 --- a/spec/builders/__snapshots__/tailwind_form_builder/text_area.snap +++ b/spec/builders/__snapshots__/tailwind_form_builder/text_area.snap @@ -1,2 +1,2 @@ -
\ No newline at end of file diff --git a/spec/builders/__snapshots__/tailwind_form_builder/text_field.snap b/spec/builders/__snapshots__/tailwind_form_builder/text_field.snap index 98ecdb9a7d..3be2122502 100644 --- a/spec/builders/__snapshots__/tailwind_form_builder/text_field.snap +++ b/spec/builders/__snapshots__/tailwind_form_builder/text_field.snap @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/spec/builders/__snapshots__/tailwind_form_builder/text_field_with_class.snap b/spec/builders/__snapshots__/tailwind_form_builder/text_field_with_class.snap index 97d64200d1..b098bfe0b4 100644 --- a/spec/builders/__snapshots__/tailwind_form_builder/text_field_with_class.snap +++ b/spec/builders/__snapshots__/tailwind_form_builder/text_field_with_class.snap @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/spec/builders/__snapshots__/tailwind_form_builder/text_field_with_leading_icon.snap b/spec/builders/__snapshots__/tailwind_form_builder/text_field_with_leading_icon.snap new file mode 100644 index 0000000000..2f33444ad5 --- /dev/null +++ b/spec/builders/__snapshots__/tailwind_form_builder/text_field_with_leading_icon.snap @@ -0,0 +1 @@ +
{:class=>"pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"}
\ No newline at end of file diff --git a/spec/builders/tailwind_form_builder_spec.rb b/spec/builders/tailwind_form_builder_spec.rb index f0ffe32e40..1496f603f3 100644 --- a/spec/builders/tailwind_form_builder_spec.rb +++ b/spec/builders/tailwind_form_builder_spec.rb @@ -17,6 +17,13 @@ class TestHelper < ActionView::Base expect(builder.text_field(:username)).to match_snapshot('tailwind_form_builder/text_field') end + context 'when the text field includes a leading icon' do + it 'returns a tailwind styled text field' do + expect(builder.text_field(:username, leading_icon: true)) + .to match_snapshot('tailwind_form_builder/text_field_with_leading_icon') + end + end + context 'when the options contains classes' do it 'append the class to the text field' do expect(builder.text_field(:username, { class: 'test-class' })) diff --git a/spec/models/project_submission_spec.rb b/spec/models/project_submission_spec.rb index 6cb21524d8..cacb6d6b68 100644 --- a/spec/models/project_submission_spec.rb +++ b/spec/models/project_submission_spec.rb @@ -16,7 +16,11 @@ it { is_expected.to allow_value('https://www.github.com/fff').for(:live_preview_url) } it { is_expected.not_to allow_value('not_a_url').for(:live_preview_url) } - it { is_expected.to validate_uniqueness_of(:user_id).scoped_to(:lesson_id) } + it do + expect(project_submission).to validate_uniqueness_of(:user_id) + .scoped_to(:lesson_id) + .with_message('You have already submitted a project for this lesson') + end context 'when live preview is not allowed' do subject(:project_submission) do diff --git a/spec/support/pages/project_submissions/form.rb b/spec/support/pages/project_submissions/form.rb index cbd698b506..d5d55aad78 100644 --- a/spec/support/pages/project_submissions/form.rb +++ b/spec/support/pages/project_submissions/form.rb @@ -31,6 +31,11 @@ def make_private self end + def v2_make_private + choose 'project_submission_is_public_false' + self + end + def submit find(:test_id, 'submit-btn').click self diff --git a/spec/system/v2_lesson_project_submissions/add_submission_spec.rb b/spec/system/v2_lesson_project_submissions/add_submission_spec.rb new file mode 100644 index 0000000000..896d10b63a --- /dev/null +++ b/spec/system/v2_lesson_project_submissions/add_submission_spec.rb @@ -0,0 +1,64 @@ +require 'rails_helper' + +RSpec.describe 'Add a Project Submission' do + let(:lesson) { create(:lesson, :project) } + + before do + Flipper.enable(:v2_project_submissions) + end + + after do + Flipper.disable(:v2_project_submissions) + end + + context 'when a user is signed in' do + let(:user) { create(:user) } + let(:another_user) { create(:user) } + + before do + sign_in(user) + visit lesson_path(lesson) + end + + it 'successfully adds a submission' do + form = Pages::ProjectSubmissions::Form.new.open.fill_in.submit + + within(:test_id, 'submissions-list') do + expect(page).to have_content(user.username) + end + + expect(page).not_to have_button('Add Solution') + end + + context 'when setting a submission as private' do + it 'will display the submission for the submission owner but not for other users' do + form = Pages::ProjectSubmissions::Form.new.open.fill_in + + form.v2_make_private + click_on 'Save' + + within(:test_id, 'submissions-list') do + page.driver.refresh + expect(page).to have_content(user.username) + end + + using_session('another_user') do + sign_in(another_user) + visit lesson_path(lesson) + + within(:test_id, 'submissions-list') do + expect(page).not_to have_content(user.username) + end + end + end + end + end + + context 'when a user is not signed in' do + it 'they cannot add a project submission' do + visit lesson_path(lesson) + + expect(page).not_to have_button('Add Solution') + end + end +end