Skip to content

Commit 7673d18

Browse files
PM-10084: Move save button into navigation bar (#775)
1 parent 7de1657 commit 7673d18

File tree

77 files changed

+195
-128
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+195
-128
lines changed

BitwardenShared/UI/Auth/Landing/SelfHosted/SelfHostedView.swift

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ struct SelfHostedView: View {
1616
VStack(spacing: 16) {
1717
selfHostedEnvironment
1818
customEnvironment
19-
saveButton
2019
}
2120
.textFieldConfiguration(.url)
2221
.navigationBar(title: Localizations.settings, titleDisplayMode: .inline)
@@ -25,6 +24,10 @@ struct SelfHostedView: View {
2524
cancelToolbarItem {
2625
store.send(.dismiss)
2726
}
27+
28+
saveToolbarItem {
29+
await store.perform(.saveEnvironment)
30+
}
2831
}
2932
}
3033

@@ -77,18 +80,6 @@ struct SelfHostedView: View {
7780
.padding(.top, 8)
7881
}
7982

80-
/// The save button.
81-
private var saveButton: some View {
82-
AsyncButton {
83-
await store.perform(.saveEnvironment)
84-
} label: {
85-
Text(Localizations.save)
86-
}
87-
.accessibilityIdentifier("SaveButton")
88-
.buttonStyle(.primary())
89-
.padding(.top, 8)
90-
}
91-
9283
/// The self-hosted environment section.
9384
private var selfHostedEnvironment: some View {
9485
section(

BitwardenShared/UI/Auth/Landing/SelfHosted/SelfHostedViewTests.swift

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,47 @@ import XCTest
44
@testable import BitwardenShared
55

66
class SelfHostedViewTests: BitwardenTestCase {
7-
let subject = SelfHostedView(store: Store(processor: StateProcessor(state: SelfHostedState())))
7+
// MARK: Properties
8+
9+
var processor: MockProcessor<SelfHostedState, SelfHostedAction, SelfHostedEffect>!
10+
var subject: SelfHostedView!
11+
12+
// MARK: Setup & Teardown
13+
14+
override func setUp() {
15+
super.setUp()
16+
17+
processor = MockProcessor(state: SelfHostedState())
18+
19+
subject = SelfHostedView(store: Store(processor: processor))
20+
}
21+
22+
override func tearDown() {
23+
super.tearDown()
24+
25+
subject = nil
26+
}
27+
28+
// MARK: Tests
29+
30+
/// Tapping the cancel button dispatches the `.dismiss` action.
31+
func test_cancelButton_tap() throws {
32+
let button = try subject.inspect().find(button: Localizations.cancel)
33+
try button.tap()
34+
XCTAssertEqual(processor.dispatchedActions.last, .dismiss)
35+
}
36+
37+
/// Tapping the save button dispatches the `.saveEnvironment` action.
38+
func test_saveButton_tap() async throws {
39+
let button = try subject.inspect().find(asyncButton: Localizations.save)
40+
try await button.tap()
41+
XCTAssertEqual(processor.effects.last, .saveEnvironment)
42+
}
43+
44+
// MARK: Snapshots
845

946
/// Tests that the view renders correctly.
1047
func test_viewRender() {
11-
assertSnapshot(of: subject, as: .defaultPortrait)
48+
assertSnapshot(of: subject.navStackWrapped, as: .defaultPortrait)
1249
}
1350
}
2.24 KB
Loading

BitwardenShared/UI/Platform/Application/Extensions/View+Toolbar.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ extension View {
4848
.accessibilityIdentifier("EditItemButton")
4949
}
5050

51+
/// Returns a toolbar button configured for saving an item.
52+
///
53+
/// - Parameter action: The action to perform when the save button is tapped.
54+
/// - Returns: A `Button` configured for saving an item.
55+
///
56+
func saveToolbarButton(action: @escaping () async -> Void) -> some View {
57+
toolbarButton(Localizations.save, action: action)
58+
.accessibilityIdentifier("SaveButton")
59+
}
60+
5161
/// Returns a `Button` that displays an image for use in a toolbar.
5262
///
5363
/// - Parameters:
@@ -140,4 +150,15 @@ extension View {
140150
optionsToolbarMenu(content: content)
141151
}
142152
}
153+
154+
/// A `ToolbarItem` for views with a save button.
155+
///
156+
/// - Parameter action: The action to perform when the save button is tapped.
157+
/// - Returns: A `ToolbarItem` with a save button.
158+
///
159+
func saveToolbarItem(_ action: @escaping () async -> Void) -> some ToolbarContent {
160+
ToolbarItem(placement: .topBarTrailing) {
161+
saveToolbarButton(action: action)
162+
}
163+
}
143164
}

BitwardenShared/UI/Platform/Settings/Settings/Vault/Folders/AddEditFolder/AddEditFolderView.swift

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ struct AddEditFolderView: View {
3131
cancelToolbarItem {
3232
store.send(.dismiss)
3333
}
34+
35+
saveToolbarItem {
36+
await store.perform(.saveTapped)
37+
}
3438
}
3539
}
3640

@@ -39,24 +43,28 @@ struct AddEditFolderView: View {
3943
content
4044
.navigationBar(title: Localizations.editFolder, titleDisplayMode: .inline)
4145
.toolbar {
42-
optionsToolbarItem {
43-
AsyncButton(Localizations.delete, role: .destructive) {
44-
await store.perform(.deleteTapped)
45-
}
46-
}
47-
4846
cancelToolbarItem {
4947
store.send(.dismiss)
5048
}
49+
50+
ToolbarItemGroup(placement: .topBarTrailing) {
51+
saveToolbarButton {
52+
await store.perform(.saveTapped)
53+
}
54+
55+
optionsToolbarMenu {
56+
AsyncButton(Localizations.delete, role: .destructive) {
57+
await store.perform(.deleteTapped)
58+
}
59+
}
60+
}
5161
}
5262
}
5363

5464
/// The content of the view in either mode.
5565
private var content: some View {
5666
VStack(alignment: .leading, spacing: 20) {
5767
nameEntryTextField
58-
59-
saveButton
6068
}
6169
.scrollView()
6270
}
@@ -71,13 +79,4 @@ struct AddEditFolderView: View {
7179
)
7280
)
7381
}
74-
75-
/// The save button.
76-
private var saveButton: some View {
77-
AsyncButton(Localizations.save) {
78-
await store.perform(.saveTapped)
79-
}
80-
.accessibilityIdentifier("SaveButton")
81-
.buttonStyle(.primary())
82-
}
8382
}

BitwardenShared/UI/Platform/Settings/Settings/Vault/Folders/AddEditFolder/AddEditFolderViewTests.swift

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,31 +61,48 @@ class AddEditFolderViewTests: BitwardenTestCase {
6161
XCTAssertEqual(processor.dispatchedActions.last, .folderNameTextChanged("text"))
6262
}
6363

64-
/// Tapping the save button performs the `.saveTapped` effect.
65-
func test_saveButton_tap() async throws {
64+
/// Tapping the save button in add mode performs the `.saveTapped` effect.
65+
func test_saveButton_tapAdd() async throws {
6666
let button = try subject.inspect().find(asyncButton: Localizations.save)
6767
try await button.tap()
6868

6969
XCTAssertEqual(processor.effects.last, .saveTapped)
7070
}
7171

72+
/// Tapping the save button in edit mode performs the `.saveTapped` effect.
73+
func test_saveButton_tapEdit() async throws {
74+
processor.state.mode = .edit(.fixture())
75+
let button = try subject.inspect().find(asyncButton: Localizations.save)
76+
try await button.tap()
77+
XCTAssertEqual(processor.effects.last, .saveTapped)
78+
}
79+
7280
// MARK: Snapshots
7381

7482
/// Tests the view renders correctly when the text field is empty.
7583
func test_snapshot_add_empty() {
76-
assertSnapshots(matching: subject, as: [.defaultPortrait, .defaultPortraitDark, .defaultPortraitAX5])
84+
assertSnapshots(
85+
matching: subject.navStackWrapped,
86+
as: [.defaultPortrait, .defaultPortraitDark, .defaultPortraitAX5]
87+
)
7788
}
7889

7990
/// Tests the view renders correctly when the text field is populated.
8091
func test_snapshot_add_populated() {
8192
processor.state.folderName = "Super cool folder name"
82-
assertSnapshots(matching: subject, as: [.defaultPortrait, .defaultPortraitDark, .defaultPortraitAX5])
93+
assertSnapshots(
94+
matching: subject.navStackWrapped,
95+
as: [.defaultPortrait, .defaultPortraitDark, .defaultPortraitAX5]
96+
)
8397
}
8498

8599
/// Tests the view renders correctly when the text field is populated.
86100
func test_snapshot_edit_populated() {
87101
processor.state.mode = .edit(.fixture())
88102
processor.state.folderName = "Super cool folder name"
89-
assertSnapshots(matching: subject, as: [.defaultPortrait, .defaultPortraitDark, .defaultPortraitAX5])
103+
assertSnapshots(
104+
matching: subject.navStackWrapped,
105+
as: [.defaultPortrait, .defaultPortraitDark, .defaultPortraitAX5]
106+
)
90107
}
91108
}
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)