Wire up comboboxes
This commit is contained in:
@@ -8,6 +8,7 @@ gem "importmap-rails"
|
||||
gem "propshaft"
|
||||
gem "stimulus-rails"
|
||||
gem "turbo-rails"
|
||||
gem "hotwire_combobox", github: "josefarias/hotwire_combobox", branch: :main
|
||||
|
||||
# Deployment and drivers
|
||||
gem "bootsnap", require: false
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
GIT
|
||||
remote: https://github.com/josefarias/hotwire_combobox.git
|
||||
revision: 57ee6c9087320fab9383a07fa919fe75e0ea72ba
|
||||
branch: main
|
||||
specs:
|
||||
hotwire_combobox (0.3.2)
|
||||
platform_agent (>= 1.0.1)
|
||||
rails (>= 7.0.7.2)
|
||||
stimulus-rails (>= 1.2)
|
||||
turbo-rails (>= 1.2)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@@ -157,6 +168,9 @@ GEM
|
||||
parser (3.3.5.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
platform_agent (1.0.1)
|
||||
activesupport (>= 5.2.0)
|
||||
useragent (~> 0.16.3)
|
||||
propshaft (1.1.0)
|
||||
actionpack (>= 7.0.0)
|
||||
activesupport (>= 7.0.0)
|
||||
@@ -319,6 +333,7 @@ DEPENDENCIES
|
||||
brakeman
|
||||
capybara
|
||||
debug
|
||||
hotwire_combobox!
|
||||
importmap-rails
|
||||
propshaft
|
||||
puma (>= 5.0)
|
||||
|
||||
@@ -155,6 +155,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
select {
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
:root {
|
||||
--hw-option-bg-color: var(--color-ink-reversed);
|
||||
--hw-active-bg-color: var(--color-selected);
|
||||
--hw-border-color: var(--color-subtle-dark);
|
||||
--hw-border-radius: 1em;
|
||||
}
|
||||
|
||||
.hw-combobox {
|
||||
turbo-frame {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.hw-combobox__main__wrapper {
|
||||
background-color: var(--color-ink-reversed);
|
||||
}
|
||||
|
||||
.hw-combobox__input {
|
||||
background-color: var(--color-ink-reversed);
|
||||
}
|
||||
}
|
||||
|
||||
.bubble__meta {
|
||||
.hw-combobox {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -0.25rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hw-combobox__main__wrapper {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
class Assignments::SwapsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def create
|
||||
@bubble.swap_assignment incoming_assignee, outgoing_assignee
|
||||
redirect_to @bubble
|
||||
end
|
||||
|
||||
private
|
||||
def incoming_assignee
|
||||
@bucket.users.active.find params.expect(:incoming_assignee_id)
|
||||
end
|
||||
|
||||
def outgoing_assignee
|
||||
@bucket.users.active.find params.expect(:outgoing_assignee_id)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class Assignments::TogglesController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def create
|
||||
@bubble.toggle_assignment assignee
|
||||
redirect_to @bubble
|
||||
end
|
||||
|
||||
private
|
||||
def assignee
|
||||
@bucket.users.active.find params.expect(:assignee_id)
|
||||
end
|
||||
end
|
||||
@@ -1,19 +0,0 @@
|
||||
class AssignmentsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def create
|
||||
@bubble.assign(find_assignee)
|
||||
redirect_to @bubble
|
||||
end
|
||||
|
||||
private
|
||||
def find_assignee
|
||||
@bucket.users.active.find(params.expect(:assignee_id))
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
class Bubbles::TagsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def index
|
||||
@tags = Current.account.tags.search params[:q]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
class Bubbles::UsersController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def index
|
||||
@users = @bucket.users.active.search params[:q]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
class Taggings::SwapsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def create
|
||||
@bubble.swap_tag incoming_tag, outgoing_tag
|
||||
redirect_to @bubble
|
||||
end
|
||||
|
||||
private
|
||||
def incoming_tag
|
||||
if params[:incoming_tag_title]
|
||||
Tag.new title: params[:incoming_tag_title]
|
||||
else
|
||||
Current.account.tags.find params.expect(:incoming_tag_id)
|
||||
end
|
||||
end
|
||||
|
||||
def outgoing_tag
|
||||
Current.account.tags.find params.expect(:outgoing_tag_id)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
class Taggings::TogglesController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def create
|
||||
@bubble.toggle_tag tag
|
||||
redirect_to @bubble
|
||||
end
|
||||
|
||||
private
|
||||
def tag
|
||||
if params[:tag_title]
|
||||
Tag.new title: params[:tag_title]
|
||||
else
|
||||
Current.account.tags.find params.expect(:tag_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,23 +0,0 @@
|
||||
class TagsController < ApplicationController
|
||||
include BucketScoped
|
||||
|
||||
before_action :set_bubble, only: %i[ new create ]
|
||||
|
||||
def index
|
||||
@tags = Current.account.tags.order(:title)
|
||||
end
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
# FIXME: Should move this to a taggings controller to separate tag administration from applying
|
||||
def create
|
||||
@bubble.tag(params.require(:tag).expect(:title))
|
||||
redirect_to @bubble
|
||||
end
|
||||
|
||||
private
|
||||
def set_bubble
|
||||
@bubble = @bucket.bubbles.find(params[:bubble_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "element" ]
|
||||
|
||||
connect() {
|
||||
this.observer = new IntersectionObserver(([entry]) => {
|
||||
if (entry.isIntersecting) this.elementTarget.focus()
|
||||
})
|
||||
this.observer.observe(this.elementTarget)
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.observer.disconnect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
closeOnFocusOutside(event) {
|
||||
if (!this.element.contains(event.relatedTarget)) {
|
||||
this.element.removeAttribute("open")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,4 @@ class Assignment < ApplicationRecord
|
||||
|
||||
belongs_to :assignee, class_name: "User"
|
||||
belongs_to :assigner, class_name: "User"
|
||||
|
||||
validates :assignee, uniqueness: { scope: :bubble }
|
||||
end
|
||||
|
||||
@@ -10,12 +10,30 @@ module Bubble::Assignable
|
||||
scope :assigned_by, ->(users) { joins(:assignments).where(assignments: { assigner: users }).distinct }
|
||||
end
|
||||
|
||||
def assign(users, assigner: Current.user)
|
||||
assignee_rows = Array(users).collect { |user| { assignee_id: user.id, assigner_id: assigner.id, bubble_id: id } }
|
||||
def assign(user, assigner: Current.user)
|
||||
assignments.create! assignee: user, assigner: assigner
|
||||
track_event :assigned, assignee_ids: [ user.id ]
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
# Already assigned
|
||||
end
|
||||
|
||||
def unassign(user)
|
||||
destructions = assignments.destroy_by assignee: user
|
||||
track_event :unassigned, assignee_ids: [ user.id ] if destructions.any?
|
||||
end
|
||||
|
||||
def swap_assignment(incoming, outgoing)
|
||||
transaction do
|
||||
Assignment.insert_all assignee_rows
|
||||
track_event :assigned, assignee_ids: assignee_rows.pluck(:assignee_id)
|
||||
unassign outgoing
|
||||
assign incoming unless incoming == outgoing
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_assignment(user)
|
||||
assigned_to?(user) ? unassign(user) : assign(user)
|
||||
end
|
||||
|
||||
def assigned_to?(user)
|
||||
assignments.exists? assignee: user
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,28 @@ module Bubble::Taggable
|
||||
scope :tagged_with, ->(tags) { joins(:taggings).where(taggings: { tag: tags }) }
|
||||
end
|
||||
|
||||
def tag(title)
|
||||
taggings.create! tag: bucket.account.tags.find_or_create_by!(title: title)
|
||||
def tag(tag)
|
||||
taggings.create! tag: tag
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
# Already tagged
|
||||
end
|
||||
|
||||
def untag(tag)
|
||||
taggings.destroy_by tag: tag
|
||||
end
|
||||
|
||||
def swap_tag(incoming, outgoing)
|
||||
transaction do
|
||||
untag outgoing
|
||||
tag incoming unless incoming == outgoing
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_tag(tag)
|
||||
tagged_with?(tag) ? untag(tag) : tag(tag)
|
||||
end
|
||||
|
||||
def tagged_with?(tag)
|
||||
tags.include? tag
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,6 +21,8 @@ class EventSummary < ApplicationRecord
|
||||
"Added by #{event.creator.name} #{time_ago_in_words(event.created_at)} ago."
|
||||
when "assigned"
|
||||
"Assigned to #{event.assignees.pluck(:name).to_sentence} #{time_ago_in_words(event.created_at)} ago."
|
||||
when "unassigned"
|
||||
"Unassigned from #{event.assignees.pluck(:name).to_sentence} #{time_ago_in_words(event.created_at)} ago."
|
||||
when "staged"
|
||||
"#{event.creator.name} moved this to '#{event.stage_name}'."
|
||||
when "unstaged"
|
||||
|
||||
+7
-1
@@ -1,12 +1,18 @@
|
||||
class Tag < ApplicationRecord
|
||||
include Filterable
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :account, default: -> { Current.account }
|
||||
|
||||
has_many :taggings, dependent: :destroy
|
||||
has_many :bubbles, through: :taggings
|
||||
|
||||
scope :search, ->(query) { where "title LIKE ?", "%#{query}%" }
|
||||
|
||||
def hashtag
|
||||
"#" + title
|
||||
end
|
||||
|
||||
def to_combobox_display
|
||||
title
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,6 +23,7 @@ class User < ApplicationRecord
|
||||
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :alphabetically, -> { order("LOWER(name)") }
|
||||
scope :search, ->(query) { where "name LIKE ?", "%#{query}%" }
|
||||
|
||||
def initials
|
||||
name.to_s.scan(/\b\p{L}/).join.upcase
|
||||
@@ -40,6 +41,10 @@ class User < ApplicationRecord
|
||||
other != self
|
||||
end
|
||||
|
||||
def to_combobox_display
|
||||
name
|
||||
end
|
||||
|
||||
private
|
||||
def deactived_email_address
|
||||
email_address.sub(/@/, "-deactivated-#{SecureRandom.uuid}@")
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<% bubble.assignments.each do |assignment| %>
|
||||
<% bubble.assignees.each do |assignee| %>
|
||||
<div class="bubble__bubble bubble__meta bubble__assignee">
|
||||
<%= form_with url: bucket_bubble_assignments_path(bubble.bucket, bubble), data: { controller: "form" } do |form| %>
|
||||
<label class="btn btn--plain position-relative fill-transparent">
|
||||
<%= avatar_image_tag assignment.assignee, loading: :lazy, class: "avatar flex-item-no-shrink" %>
|
||||
|
||||
<%= form.collection_select :assignee_id, bubble.bucket.users.active, :id, :name, { prompt: "Assign to…", selected: bubble.assignees.pluck(:id) },
|
||||
class: "input input--hidden txt-medium",
|
||||
data: { action: "change->form#submit click->form#showPicker" } %>
|
||||
<details name="assignments" class="position-relative" data-controller="expandable" data-action="focusout->expandable#closeOnFocusOutside">
|
||||
<summary class="btn btn--plain fill-transparent">
|
||||
<%= avatar_image_tag assignee, loading: :lazy, class: "avatar flex-item-no-shrink" %>
|
||||
<span class="for-screen-reader">Assign to…</span>
|
||||
</label>
|
||||
<% end %>
|
||||
</summary>
|
||||
|
||||
<%= form_with url: bucket_bubble_assignment_swaps_path(bubble.bucket, bubble), data: { controller: "form" } do |form| %>
|
||||
<%= form.hidden_field :outgoing_assignee_id, value: assignee.id %>
|
||||
<%= form.combobox :incoming_assignee_id, bucket_bubble_users_path(bubble.bucket, bubble),
|
||||
id: dom_id(assignee, :assignment), required: true, preload: true,
|
||||
data: { controller: "autofocus", action: "hw-combobox:selection->form#submit" } %>
|
||||
<% end %>
|
||||
</details>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
<% @filter ||= Current.user.filters.last %>
|
||||
|
||||
<% bubble.tags.last(3).each do |tag| %>
|
||||
<%= link_to tag.hashtag, bubbles_path(@filter&.to_params&.merge(tag_ids: [ tag.id ])), class: "bubble__bubble bubble__meta bubble__tag" %>
|
||||
<div class="bubble__bubble bubble__meta bubble__tag">
|
||||
<details name="assignments" class="position-relative" data-controller="expandable" data-action="focusout->expandable#closeOnFocusOutside">
|
||||
<summary class="btn btn--plain fill-transparent" style="--btn-color: <%= bubble.color %>">
|
||||
<%= tag.hashtag %>
|
||||
</summary>
|
||||
|
||||
<%= form_with url: bucket_bubble_tagging_swaps_path(bubble.bucket, bubble), data: { controller: "form" } do |form| %>
|
||||
<%= form.hidden_field :outgoing_tag_id, value: tag.id %>
|
||||
<%= form.combobox :incoming_tag_id, bucket_bubble_tags_path(bubble.bucket, bubble),
|
||||
id: dom_id(tag, :tagging), required: true, preload: true, name_when_new: "incoming_tag_title",
|
||||
data: { controller: "autofocus", action: "hw-combobox:selection->form#submit" } %>
|
||||
<% end %>
|
||||
</details>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -25,44 +25,8 @@
|
||||
</label>
|
||||
<% end %>
|
||||
|
||||
<%= form_with url: bucket_bubble_assignments_path(@bubble.bucket, @bubble), data: { controller: "form" } do |form| %>
|
||||
<label class="btn full-width justify-start borderless">
|
||||
<%= image_tag "person.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Assign to someone</span>
|
||||
<%= form.collection_select :assignee_id, @bubble.bucket.users.active, :id, :name, { prompt: "Assign to…", selected: @bubble.assignees.pluck(:id) },
|
||||
class: "input input--hidden txt-medium",
|
||||
data: { action: "change->form#submit click->form#showPicker" } %>
|
||||
</label>
|
||||
<% end %>
|
||||
|
||||
<div class="position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close">
|
||||
<button class="btn full-width justify-start borderless" data-action="dialog#toggle">
|
||||
<%= image_tag "tag.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Tag this</span>
|
||||
</button>
|
||||
|
||||
<dialog class="tag-picker panel fill-white shadow gap-half align-center" data-dialog-target="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<%= image_tag "tag.svg", aria: { hidden: true }, size: 24 %>
|
||||
|
||||
<%= form_with model: Tag.new, url: bucket_bubble_tags_path(@bubble.bucket, @bubble), class: "min-width flex-item-grow", data: { turbo_frame: "_top" } do |form| %>
|
||||
<%= form.text_field :title, class: "input", autofocus: "on", list: "tags-list" %>
|
||||
<%= form.submit "Create Tag", hidden: true %>
|
||||
|
||||
<datalist id="tags-list">
|
||||
<%= Current.account.tags.each do |tag| %>
|
||||
<option value="<%= tag.title %>"></option>
|
||||
<% end %>
|
||||
</datalist>
|
||||
<% end %>
|
||||
|
||||
<form method="dialog">
|
||||
<button class="btn txt-small" title="Close (esc)">
|
||||
<%= image_tag "remove.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span class="for-screen-reader">Close</span>
|
||||
</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
<%= render "bubbles/sidebar/assignment", bubble: @bubble %>
|
||||
<%= render "bubbles/sidebar/tag", bubble: @bubble %>
|
||||
|
||||
<% if @bubble.image.attached? %>
|
||||
<%= button_to bucket_bubble_image_path(@bubble.bucket, @bubble), method: :delete, class: "btn full-width justify-start borderless" do %>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<details name="assignments" class="position-relative" data-controller="expandable" data-action="focusout->expandable#closeOnFocusOutside">
|
||||
<summary class="btn full-width justify-start borderless">
|
||||
<%= image_tag "person.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Assign to someone</span>
|
||||
</summary>
|
||||
|
||||
<%= form_with url: bucket_bubble_assignment_toggles_path(bubble.bucket, bubble), data: { controller: "form" } do |form| %>
|
||||
<%= form.combobox :assignee_id, bucket_bubble_users_path(bubble.bucket, bubble), required: true, preload: true,
|
||||
data: { controller: "autofocus", action: "hw-combobox:selection->form#submit" } %>
|
||||
<% end %>
|
||||
</details>
|
||||
@@ -0,0 +1,12 @@
|
||||
<details name="assignments" class="position-relative" data-controller="expandable" data-action="focusout->expandable#closeOnFocusOutside">
|
||||
<summary class="btn full-width justify-start borderless">
|
||||
<%= image_tag "tag.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Tag this</span>
|
||||
</summary>
|
||||
|
||||
<%= form_with url: bucket_bubble_tagging_toggles_path(bubble.bucket, bubble), data: { controller: "form" } do |form| %>
|
||||
<%= form.combobox :tag_id, bucket_bubble_tags_path(bubble.bucket, bubble),
|
||||
required: true, preload: true, name_when_new: "tag_title",
|
||||
data: { controller: "autofocus", action: "hw-combobox:selection->form#submit" } %>
|
||||
<% end %>
|
||||
</details>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="flex gap-half justify-space-between align-center" style="--btn-icon-size: 1rem">
|
||||
<%= tag.hashtag %>
|
||||
|
||||
<% if bubble.tagged_with?(tag) %>
|
||||
<%= image_tag "check.svg", aria: { hidden: "true" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<%= async_combobox_options @tags, render_in: { partial: "bubbles/tags/select_option", as: :tag, locals: { bubble: @bubble } } %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="flex gap-half justify-space-between align-center" style="--btn-icon-size: 1rem">
|
||||
<%= user.name %>
|
||||
|
||||
<% if bubble.assigned_to?(user) %>
|
||||
<%= image_tag "check.svg", aria: { hidden: "true" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<%= async_combobox_options @users, render_in: { partial: "bubbles/users/select_option", as: :user, locals: { bubble: @bubble } } %>
|
||||
@@ -12,6 +12,7 @@
|
||||
<%= csp_meta_tag %>
|
||||
<%= tag.meta name: "current-user-id", content: Current.user.id if Current.user %>
|
||||
|
||||
<%= combobox_style_tag %>
|
||||
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
|
||||
<%= javascript_importmap_tags %>
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class ActionView::Helpers::FormBuilder
|
||||
def combobox(...)
|
||||
super do |combobox|
|
||||
combobox.customize_main_wrapper class: "btn"
|
||||
combobox.customize_input data: { autofocus_target: "element" }
|
||||
yield combobox if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
+12
-4
@@ -16,20 +16,28 @@ Rails.application.routes.draw do
|
||||
|
||||
resources :buckets do
|
||||
resources :bubbles do
|
||||
resources :assignments
|
||||
resources :boosts
|
||||
resources :comments
|
||||
resources :tags, shallow: true
|
||||
|
||||
scope module: :bubbles do
|
||||
resource :image
|
||||
resource :pop
|
||||
resource :stage_picker
|
||||
resources :stagings
|
||||
resources :tags
|
||||
resources :users
|
||||
end
|
||||
|
||||
namespace :assignments, as: :assignment do
|
||||
resources :swaps
|
||||
resources :toggles
|
||||
end
|
||||
|
||||
namespace :taggings, as: :tagging do
|
||||
resources :swaps
|
||||
resources :toggles
|
||||
end
|
||||
end
|
||||
|
||||
resources :tags, only: :index
|
||||
end
|
||||
|
||||
resources :filters
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
class MakeTaggingsUniquePerBubble < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
remove_index :taggings, :bubble_id
|
||||
add_index :taggings, %i[ bubble_id tag_id ], unique: true
|
||||
end
|
||||
end
|
||||
Generated
+2
-2
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2024_11_19_201943) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2024_11_20_222444) do
|
||||
create_table "accesses", force: :cascade do |t|
|
||||
t.integer "bucket_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
@@ -187,7 +187,7 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_19_201943) do
|
||||
t.integer "tag_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["bubble_id"], name: "index_taggings_on_bubble_id"
|
||||
t.index ["bubble_id", "tag_id"], name: "index_taggings_on_bubble_id_and_tag_id", unique: true
|
||||
t.index ["tag_id"], name: "index_taggings_on_tag_id"
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
require "test_helper"
|
||||
|
||||
class Assignments::SwapsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "swap with same user" do
|
||||
assert_changes "bubbles(:logo).assigned_to?(users(:kevin))", from: true, to: false do
|
||||
post bucket_bubble_assignment_swaps_url(buckets(:writebook), bubbles(:logo)), params: {
|
||||
incoming_assignee_id: users(:kevin).id,
|
||||
outgoing_assignee_id: users(:kevin).id
|
||||
}
|
||||
end
|
||||
assert_redirected_to bubbles(:logo)
|
||||
end
|
||||
|
||||
test "swap with another user" do
|
||||
assert_changes "bubbles(:logo).assigned_to?(users(:david))", from: false, to: true do
|
||||
assert_changes "bubbles(:logo).assigned_to?(users(:kevin))", from: true, to: false do
|
||||
post bucket_bubble_assignment_swaps_url(buckets(:writebook), bubbles(:logo)), params: {
|
||||
incoming_assignee_id: users(:david).id,
|
||||
outgoing_assignee_id: users(:kevin).id
|
||||
}
|
||||
end
|
||||
end
|
||||
assert_redirected_to bubbles(:logo)
|
||||
end
|
||||
|
||||
test "swap with another user when already assigned" do
|
||||
assert_no_changes "bubbles(:logo).assigned_to?(users(:jz))" do
|
||||
assert_changes "bubbles(:logo).assigned_to?(users(:kevin))", from: true, to: false do
|
||||
post bucket_bubble_assignment_swaps_url(buckets(:writebook), bubbles(:logo)), params: {
|
||||
incoming_assignee_id: users(:jz).id,
|
||||
outgoing_assignee_id: users(:kevin).id
|
||||
}
|
||||
end
|
||||
end
|
||||
assert_redirected_to bubbles(:logo)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
require "test_helper"
|
||||
|
||||
class Assignments::TogglesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "create" do
|
||||
assert_changes "bubbles(:logo).assigned_to?(users(:david))", from: false, to: true do
|
||||
post bucket_bubble_assignment_toggles_url(buckets(:writebook), bubbles(:logo)), params: { assignee_id: users(:david).id }
|
||||
end
|
||||
assert_redirected_to bubbles(:logo)
|
||||
|
||||
assert_changes "bubbles(:logo).assigned_to?(users(:david))", from: true, to: false do
|
||||
post bucket_bubble_assignment_toggles_url(buckets(:writebook), bubbles(:logo)), params: { assignee_id: users(:david).id }
|
||||
end
|
||||
assert_redirected_to bubbles(:logo)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
require "test_helper"
|
||||
|
||||
class Taggings::SwapsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "swap with same tag" do
|
||||
assert_changes "bubbles(:logo).tagged_with?(tags(:web))", from: true, to: false do
|
||||
post bucket_bubble_tagging_swaps_url(buckets(:writebook), bubbles(:logo)), params: {
|
||||
incoming_tag_id: tags(:web).id,
|
||||
outgoing_tag_id: tags(:web).id
|
||||
}
|
||||
end
|
||||
assert_redirected_to bubbles(:logo)
|
||||
end
|
||||
|
||||
test "swap with another tag" do
|
||||
assert_changes "bubbles(:logo).tagged_with?(tags(:mobile))", from: false, to: true do
|
||||
assert_changes "bubbles(:logo).tagged_with?(tags(:web))", from: true, to: false do
|
||||
post bucket_bubble_tagging_swaps_url(buckets(:writebook), bubbles(:logo)), params: {
|
||||
incoming_tag_id: tags(:mobile).id,
|
||||
outgoing_tag_id: tags(:web).id
|
||||
}
|
||||
end
|
||||
end
|
||||
assert_redirected_to bubbles(:logo)
|
||||
end
|
||||
|
||||
test "swap with another tag when already tagged" do
|
||||
assert_no_changes "bubbles(:layout).tagged_with?(tags(:mobile))" do
|
||||
assert_changes "bubbles(:layout).tagged_with?(tags(:web))", from: true, to: false do
|
||||
post bucket_bubble_tagging_swaps_url(buckets(:writebook), bubbles(:layout)), params: {
|
||||
incoming_tag_id: tags(:mobile).id,
|
||||
outgoing_tag_id: tags(:web).id
|
||||
}
|
||||
end
|
||||
end
|
||||
assert_redirected_to bubbles(:layout)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
require "test_helper"
|
||||
|
||||
class Taggings::TogglesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "create" do
|
||||
assert_changes "bubbles(:logo).tagged_with?(tags(:mobile))", from: false, to: true do
|
||||
post bucket_bubble_tagging_toggles_url(buckets(:writebook), bubbles(:logo)), params: { tag_id: tags(:mobile).id }
|
||||
end
|
||||
assert_redirected_to bubbles(:logo)
|
||||
|
||||
assert_changes "bubbles(:logo).tagged_with?(tags(:mobile))", from: true, to: false do
|
||||
post bucket_bubble_tagging_toggles_url(buckets(:writebook), bubbles(:logo)), params: { tag_id: tags(:mobile).id }
|
||||
end
|
||||
assert_redirected_to bubbles(:logo)
|
||||
end
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class TagsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "create with existing tag" do
|
||||
assert_no_difference -> { accounts(:"37s").tags.count } do
|
||||
post bucket_bubble_tags_url(buckets(:writebook), bubbles(:logo)), params: { tag: { title: "Web" } }
|
||||
end
|
||||
|
||||
assert_redirected_to bubbles(:logo)
|
||||
end
|
||||
|
||||
test "create with new tag" do
|
||||
assert_difference -> { accounts(:"37s").tags.count }, +1 do
|
||||
post bucket_bubble_tags_url(buckets(:writebook), bubbles(:logo)), params: { tag: { title: "Horizons" } }
|
||||
end
|
||||
|
||||
assert_redirected_to bubbles(:logo)
|
||||
assert bubbles(:logo).tags.pluck(:title).include?("Horizons")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user