Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add record mode #105

Open
wants to merge 18 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Sources/App/Environments/AppEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ final class AppEnvironment: ObservableObject {
/// Whether the server is currently running.
@Published var isServerRunning: Bool = false

/// Whether the server is currently in record mode.
@Published var isServerRecording: Bool = false

/// The base `URL` to be used by the middleware when performing network calls in record mode.
@Published var middlewareBaseURL: URL? = nil

/// Whether or not the response should be overwritten in the record mode.
@Published var shouldOverwriteResponse: Bool = true

/// The path where the recorded responses and requests will be saved in record mode.
@Published var selectedRecordingPath: URL? = nil

/// The selected app section, selected by using the app's Sidebar.
@Published var selectedSection: SidebarSection = .server

Expand All @@ -20,8 +32,20 @@ final class AppEnvironment: ObservableObject {
/// Whether the startup settings should be shown or not.
@Published var shouldShowStartupSettings = !Logic.Settings.isWorkspaceURLValid

/// Whether or not the record mode settings are shown.
@Published var isRecordModeSettingsPresented: Bool = false

/// The global server configuration.
var serverConfiguration: ServerConfiguration? {
Logic.Settings.serverConfiguration
}

/// The global record mode middleware configuration.
var middlewareConfiguration: MiddlewareConfiguration? {
guard let baseURL = middlewareBaseURL, let hostname = serverConfiguration?.hostname, let port = serverConfiguration?.port else {
return nil
}

return MiddlewareConfiguration(baseURL: baseURL, hostname: hostname, port: port)
}
}
3 changes: 3 additions & 0 deletions Sources/App/Helpers/SFSymbol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ enum SFSymbol: String {
/// Plus circle icon.
case plusCircle = "plus.circle"

/// Start recording icon.
case startRecording = "record.circle"

/// Refresh icon.
case refresh = "arrow.triangle.2.circlepath"

Expand Down
4 changes: 2 additions & 2 deletions Sources/App/Helpers/Size.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ enum Size {
static let minimumAppHeight: CGFloat = 600

/// Minimum List width.
static let minimumListWidth: CGFloat = 370
static let minimumListWidth: CGFloat = 380

/// Minimum Detail width.
static let minimumDetailWidth: CGFloat = 400

/// Minimum Filter text field width.
static var minimumFilterTextFieldWidth: CGFloat {
minimumListWidth - 140
minimumListWidth - 160
}

/// Minimum App section width.
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/Logic/SourceTree+Logic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ extension Logic.SourceTree {
/// Enumerates the contents of a directory.
/// - Parameter url: The `URL` of the directory to scan.
/// - Returns: An array of `FileSystemNode` containing all sub-nodes of the directory.
private static func contents(of url: URL) -> [FileSystemNode] {
static func contents(of url: URL) -> [FileSystemNode] {
guard
let directoryEnumerator = FileManager.default.enumerator(
at: url,
Expand Down
43 changes: 27 additions & 16 deletions Sources/App/Mocka.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,32 @@ struct Mocka: App {

var body: some Scene {
WindowGroup {
AppSection()
.frame(
// Due to a bug of the `NavigationView` we cannot use the exactly minimum size.
// We add `5` points to be sure to not close the sidebar while resizing the view.
minWidth: Size.minimumSidebarWidth + Size.minimumListWidth + Size.minimumDetailWidth + 5,
maxWidth: .infinity,
minHeight: Size.minimumAppHeight,
maxHeight: .infinity,
alignment: .leading
)
.environmentObject(appEnvironment)
.sheet(
isPresented: $appEnvironment.shouldShowStartupSettings
) {
ServerSettings(viewModel: ServerSettingsViewModel(isShownFromSettings: false))
}
AppSection(
viewModel: AppSectionViewModel(
recordModeNetworkExchangesPublisher: appEnvironment.server.recordModeNetworkExchangesPublisher, appEnvironment: appEnvironment)
)
.frame(
// Due to a bug of the `NavigationView` we cannot use the exactly minimum size.
// We add `5` points to be sure to not close the sidebar while resizing the view.
minWidth: Size.minimumSidebarWidth + Size.minimumListWidth + Size.minimumDetailWidth + 5,
maxWidth: .infinity,
minHeight: Size.minimumAppHeight,
maxHeight: .infinity,
alignment: .leading
)
.environmentObject(appEnvironment)
.sheet(
isPresented: $appEnvironment.shouldShowStartupSettings
) {
ServerSettings(viewModel: ServerSettingsViewModel(isShownFromSettings: false))
}
.sheet(
isPresented: $appEnvironment.isRecordModeSettingsPresented
) {
RecordModeSettings(viewModel: RecordModeSettingsViewModel(appEnvironment: appEnvironment))
.environmentObject(appEnvironment)
}

}
.windowStyle(HiddenTitleBarWindowStyle())
.windowToolbarStyle(UnifiedWindowToolbarStyle())
Expand All @@ -40,6 +50,7 @@ struct Mocka: App {

Settings {
AppSettings()
.environmentObject(appEnvironment)
}
}
}
33 changes: 33 additions & 0 deletions Sources/App/Models/MiddlewareConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Mocka
//

import Foundation
import MockaServer

/// An object containing the parameters needed to configure the server's connection.
struct MiddlewareConfiguration: MiddlewareConfigurationProvider, Codable {
/// The base `URL` on which the middleware will start requests.
var baseURL: URL

/// The host part of the `URL`.
let hostname: String

/// The port listening to incoming requests.
let port: Int

/// Creates a new `ServerConnectionConfiguration` object.
/// - Parameters:
/// - baseURL: The base `URL` on which the middleware will start requests.
/// - hostname: The host part of the `URL`.
/// - port: The port listening to incoming requests.
init(
baseURL: URL,
hostname: String,
port: Int
) {
self.baseURL = baseURL
self.hostname = hostname
self.port = port
}
}
12 changes: 12 additions & 0 deletions Sources/App/Models/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ struct Request: Equatable, Hashable {
self.expectedResponse = expectedResponse
}

/// Creates a `Request` object starting from a `NetworkExchange` object.
/// - Parameter networkExchange: The `NetworkExchange` object received from the server.
init(from networkExchange: NetworkExchange) {
path = networkExchange.request.uri.path.components(separatedBy: "/")
method = HTTPMethod(rawValue: networkExchange.request.httpMethod.rawValue)!
expectedResponse = Response(
statusCode: Int(networkExchange.response.status.code),
contentType: networkExchange.response.headers.contentType ?? .applicationJSON,
headers: networkExchange.response.headers.map { HTTPHeader(key: $0.name, value: $0.value) }
)
}

/// Converts a `MockaApp.Request` into a `MockaServer.Request`.
/// - Parameters:
/// - url: The `URL` where the response file lives. This is the same the request's.
Expand Down
2 changes: 2 additions & 0 deletions Sources/App/Resources/MockaApp.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Mocka
//

import SwiftUI

/// The start and stop record mode button.
/// This button automatically handles the start and stop of the record mode
/// by using the `AppEnvironment`.
struct StartAndStopRecordModeButton: View {

// MARK: - Stored Properties

/// The app environment object.
@EnvironmentObject var appEnvironment: AppEnvironment

/// The associated ViewModel.
@StateObject var viewModel: StartAndStopRecordModeButtonViewModel = StartAndStopRecordModeButtonViewModel()

// MARK: - Body

var body: some View {
SymbolButton(
symbolName: appEnvironment.isServerRecording ? .stopCircle : .startRecording,
color: appEnvironment.isServerRunning ? Color.redEye : nil,
action: {
if appEnvironment.middlewareConfiguration == nil {
appEnvironment.isRecordModeSettingsPresented.toggle()
} else {
viewModel.startAndStopRecordMode(on: appEnvironment)
}
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// Mocka
//

import Foundation

/// The ViewModel of the `ServerToolbar`.
final class StartAndStopRecordModeButtonViewModel: ObservableObject {

// MARK: - Functions

/// Start and stop the record mode.
/// - Parameter appEnvironment: The `AppEnvironment` instance.
func startAndStopRecordMode(on appEnvironment: AppEnvironment) {
switch appEnvironment.isServerRecording {
case true:
try? appEnvironment.server.stop()

appEnvironment.isServerRunning.toggle()
appEnvironment.isServerRecording.toggle()

case false:
appEnvironment.isRecordModeSettingsPresented.toggle()
}
}
}
4 changes: 4 additions & 0 deletions Sources/App/Views/Common/Buttons/SymbolButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ struct SymbolButton: View {
/// The name of the SF Symbol.
var symbolName: SFSymbol

/// An optional foreground color for the button.
var color: Color?

/// The action to execute when the button is tapped.
var action: () -> Void

Expand All @@ -27,6 +30,7 @@ struct SymbolButton: View {
)
.buttonStyle(PlainButtonStyle())
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(color)
}
}

Expand Down
18 changes: 15 additions & 3 deletions Sources/App/Views/Sections/AppSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Mocka
//

import MockaServer
import SwiftUI

/// This is the common app section used to show all the other sections.
Expand All @@ -11,6 +12,9 @@ struct AppSection: View {

// MARK: - Stored Properties

/// The associated ViewModel.
@ObservedObject var viewModel: AppSectionViewModel

/// The app environment object.
@EnvironmentObject var appEnvironment: AppEnvironment

Expand All @@ -34,9 +38,17 @@ struct AppSection: View {
// MARK: - Previews

struct AppSectionPreview: PreviewProvider {
static let networkExchanges = [NetworkExchange](
repeating: NetworkExchange.mock,
count: 10
)

static var previews: some View {
AppSection()
.previewLayout(.fixed(width: 1024, height: 600))
.environmentObject(AppEnvironment())
AppSection(
viewModel: AppSectionViewModel(
recordModeNetworkExchangesPublisher: networkExchanges.publisher.eraseToAnyPublisher(), appEnvironment: AppEnvironment())
)
.previewLayout(.fixed(width: 1024, height: 600))
.environmentObject(AppEnvironment())
}
}
Loading