1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2026-04-02 17:40:08 +02:00

♻️ Improvements to Container

- RealShell is not reloaded during runtime (bugfix?)
- Container variables are now private(set)
- Initialization now also sets `webApi` property (new)
- It is only possible to run `bind` on a `Container` once now
  (previously known as `prepare`)
- Preparation for upcoming WebApi to replace `curl` command
  (for checking for updates)
This commit is contained in:
2025-11-18 12:04:37 +01:00
parent 58083ba443
commit 7a60435421
18 changed files with 152 additions and 134 deletions

View File

@@ -28,14 +28,14 @@
0329A9A42E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; }; 0329A9A42E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; };
0329A9A52E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; }; 0329A9A52E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; };
0329A9A62E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; }; 0329A9A62E92A69000A62A12 /* WarningManager+Evaluations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */; };
032DAC282E8BEB5B0018E01C /* RealApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealApi.swift */; }; 032DAC282E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC292E8BEB5B0018E01C /* RealApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealApi.swift */; }; 032DAC292E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC2A2E8BEB5B0018E01C /* RealApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealApi.swift */; }; 032DAC2A2E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC2B2E8BEB5B0018E01C /* RealApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealApi.swift */; }; 032DAC2B2E8BEB5B0018E01C /* RealWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC272E8BEB590018E01C /* RealWebApi.swift */; };
032DAC2D2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */; }; 032DAC2D2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */; };
032DAC2E2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */; }; 032DAC2E2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */; };
032DAC2F2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */; }; 032DAC2F2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */; };
032DAC302E8BEB6B0018E01C /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */; }; 032DAC302E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */; };
033D45982B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; }; 033D45982B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
033D45992B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; }; 033D45992B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
033D459A2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; }; 033D459A2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */; };
@@ -75,14 +75,10 @@
0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; }; 0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
0396160F2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; }; 0396160F2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
039616102E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; }; 039616102E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
039C29132E8AA163007F5FAB /* ActiveApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29122E8AA15F007F5FAB /* ActiveApi.swift */; }; 039C29182E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C29142E8AA163007F5FAB /* ActiveApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29122E8AA15F007F5FAB /* ActiveApi.swift */; }; 039C29192E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C29152E8AA163007F5FAB /* ActiveApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29122E8AA15F007F5FAB /* ActiveApi.swift */; }; 039C291A2E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C29162E8AA163007F5FAB /* ActiveApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29122E8AA15F007F5FAB /* ActiveApi.swift */; }; 039C291B2E8AA314007F5FAB /* TestableWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableWebApi.swift */; };
039C29182E8AA314007F5FAB /* TestableApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableApi.swift */; };
039C29192E8AA314007F5FAB /* TestableApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableApi.swift */; };
039C291A2E8AA314007F5FAB /* TestableApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableApi.swift */; };
039C291B2E8AA314007F5FAB /* TestableApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C29172E8AA311007F5FAB /* TestableApi.swift */; };
039C291D2E8AA39A007F5FAB /* TestableApiTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C291C2E8AA399007F5FAB /* TestableApiTest.swift */; }; 039C291D2E8AA39A007F5FAB /* TestableApiTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C291C2E8AA399007F5FAB /* TestableApiTest.swift */; };
039E1D792E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; }; 039E1D792E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
039E1D7A2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; }; 039E1D7A2E5F0F300072D13D /* ValetUpgrader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */; };
@@ -1019,8 +1015,8 @@
03263A372E86D5E800BD0415 /* UpdateScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateScheduler.swift; sourceTree = "<group>"; }; 03263A372E86D5E800BD0415 /* UpdateScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateScheduler.swift; sourceTree = "<group>"; };
0329A9A02E92A2A800A62A12 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; }; 0329A9A02E92A2A800A62A12 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WarningManager+Evaluations.swift"; sourceTree = "<group>"; }; 0329A9A22E92A68B00A62A12 /* WarningManager+Evaluations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WarningManager+Evaluations.swift"; sourceTree = "<group>"; };
032DAC272E8BEB590018E01C /* RealApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealApi.swift; sourceTree = "<group>"; }; 032DAC272E8BEB590018E01C /* RealWebApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealWebApi.swift; sourceTree = "<group>"; };
032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiProtocol.swift; sourceTree = "<group>"; }; 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebApiProtocol.swift; sourceTree = "<group>"; };
0336CAAF2B0D0CDA009A1034 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; }; 0336CAAF2B0D0CDA009A1034 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallPhpExtensionCommand.swift; sourceTree = "<group>"; }; 033D45972B0D4EC600070080 /* InstallPhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallPhpExtensionCommand.swift; sourceTree = "<group>"; };
033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovePhpExtensionCommand.swift; sourceTree = "<group>"; }; 033D459D2B0D513900070080 /* RemovePhpExtensionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovePhpExtensionCommand.swift; sourceTree = "<group>"; };
@@ -1039,8 +1035,7 @@
0392CDE52EB23B8F009176DA /* CertificateValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateValidator.swift; sourceTree = "<group>"; }; 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateValidator.swift; sourceTree = "<group>"; };
0392CDEA2EB25371009176DA /* SecurePopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurePopoverView.swift; sourceTree = "<group>"; }; 0392CDEA2EB25371009176DA /* SecurePopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurePopoverView.swift; sourceTree = "<group>"; };
0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggableEvent.swift; sourceTree = "<group>"; }; 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggableEvent.swift; sourceTree = "<group>"; };
039C29122E8AA15F007F5FAB /* ActiveApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveApi.swift; sourceTree = "<group>"; }; 039C29172E8AA311007F5FAB /* TestableWebApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableWebApi.swift; sourceTree = "<group>"; };
039C29172E8AA311007F5FAB /* TestableApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableApi.swift; sourceTree = "<group>"; };
039C291C2E8AA399007F5FAB /* TestableApiTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableApiTest.swift; sourceTree = "<group>"; }; 039C291C2E8AA399007F5FAB /* TestableApiTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableApiTest.swift; sourceTree = "<group>"; };
039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetUpgrader.swift; sourceTree = "<group>"; }; 039E1D782E5F0F2C0072D13D /* ValetUpgrader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetUpgrader.swift; sourceTree = "<group>"; };
03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageExtension.swift; sourceTree = "<group>"; }; 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageExtension.swift; sourceTree = "<group>"; };
@@ -1450,9 +1445,9 @@
039C29112E8AA159007F5FAB /* Http */ = { 039C29112E8AA159007F5FAB /* Http */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
039C29122E8AA15F007F5FAB /* ActiveApi.swift */, 032DAC272E8BEB590018E01C /* RealWebApi.swift */,
032DAC272E8BEB590018E01C /* RealApi.swift */, 039C29172E8AA311007F5FAB /* TestableWebApi.swift */,
032DAC2C2E8BEB690018E01C /* ApiProtocol.swift */, 032DAC2C2E8BEB690018E01C /* WebApiProtocol.swift */,
); );
path = Http; path = Http;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2281,6 +2276,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */, C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */,
C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */,
C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */, C4C8900228F0E28800CE5E97 /* FileSystemProtocol.swift */,
); );
path = Filesystem; path = Filesystem;
@@ -2348,6 +2344,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C4B5853D2770FE3900DA4FBE /* RealCommand.swift */, C4B5853D2770FE3900DA4FBE /* RealCommand.swift */,
C4E49DEC28F764A00026AC4E /* TestableCommand.swift */,
C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */, C4E49DE928F7643D0026AC4E /* CommandProtocol.swift */,
); );
path = Command; path = Command;
@@ -2410,6 +2407,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C46EBC4628DB9644007ACC74 /* RealShell.swift */, C46EBC4628DB9644007ACC74 /* RealShell.swift */,
C46EBC4928DB966A007ACC74 /* TestableShell.swift */,
C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */, C46EBC4328DB95F0007ACC74 /* ShellProtocol.swift */,
); );
path = Shell; path = Shell;
@@ -2418,10 +2416,6 @@
C4F787A728EF812600790735 /* Testables */ = { C4F787A728EF812600790735 /* Testables */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
039C29172E8AA311007F5FAB /* TestableApi.swift */,
C46EBC4928DB966A007ACC74 /* TestableShell.swift */,
C4AD38B128ECD9D300FA8D83 /* TestableFileSystem.swift */,
C4E49DEC28F764A00026AC4E /* TestableCommand.swift */,
C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */, C4E2E85B28FC282B003B070C /* TestableConfiguration.swift */,
); );
path = Testables; path = Testables;
@@ -2790,7 +2784,7 @@
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */, C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */, C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C43B8FD52BA9BAD3000C02BE /* UnavailableContentView.swift in Sources */, C43B8FD52BA9BAD3000C02BE /* UnavailableContentView.swift in Sources */,
032DAC2A2E8BEB5B0018E01C /* RealApi.swift in Sources */, 032DAC2A2E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */, 54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */, C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */,
0392CDEB2EB25371009176DA /* SecurePopoverView.swift in Sources */, 0392CDEB2EB25371009176DA /* SecurePopoverView.swift in Sources */,
@@ -2812,7 +2806,7 @@
C4463FCC29804BCB007B93D5 /* RCFile.swift in Sources */, C4463FCC29804BCB007B93D5 /* RCFile.swift in Sources */,
C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */, C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */,
C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */, C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */,
032DAC2E2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */, 032DAC2E2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
C4F2E4372752F0870020E974 /* BrewDiagnostics.swift in Sources */, C4F2E4372752F0870020E974 /* BrewDiagnostics.swift in Sources */,
031E2B692B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */, 031E2B692B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C4AD38B228ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */, C4AD38B228ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */,
@@ -2940,7 +2934,6 @@
C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */, C4BF56AB2949381100379603 /* FakeValetInteractor.swift in Sources */,
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */, C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */,
C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */, C451AFF62969E40F0078E617 /* HelpButton.swift in Sources */,
039C29132E8AA163007F5FAB /* ActiveApi.swift in Sources */,
C46DC7A42C7B55DC00F19D17 /* Favorites.swift in Sources */, C46DC7A42C7B55DC00F19D17 /* Favorites.swift in Sources */,
54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */, 54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */,
C4AFC4AE29C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */, C4AFC4AE29C4F32F00BF4E0D /* BrewPhpFormula.swift in Sources */,
@@ -2968,7 +2961,7 @@
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */, C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */, C493084A279F331F009C240B /* AddSiteVC.swift in Sources */,
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */, C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */,
039C29182E8AA314007F5FAB /* TestableApi.swift in Sources */, 039C29182E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
C47015022C46D6910069AAE7 /* NVAlertExtension.swift in Sources */, C47015022C46D6910069AAE7 /* NVAlertExtension.swift in Sources */,
C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */, C4E49DEA28F7643D0026AC4E /* CommandProtocol.swift in Sources */,
); );
@@ -2997,7 +2990,7 @@
C471E83728F9BB650021E251 /* DomainScanner.swift in Sources */, C471E83728F9BB650021E251 /* DomainScanner.swift in Sources */,
C456A0C82AA614BD0080144F /* PhpPreference.swift in Sources */, C456A0C82AA614BD0080144F /* PhpPreference.swift in Sources */,
C490E3BE29BCA375006D2DE6 /* Measurements.swift in Sources */, C490E3BE29BCA375006D2DE6 /* Measurements.swift in Sources */,
039C29192E8AA314007F5FAB /* TestableApi.swift in Sources */, 039C29192E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
C471E83928F9BB650021E251 /* ValetSite.swift in Sources */, C471E83928F9BB650021E251 /* ValetSite.swift in Sources */,
C471E83A28F9BB650021E251 /* FakeValetSite.swift in Sources */, C471E83A28F9BB650021E251 /* FakeValetSite.swift in Sources */,
C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */, C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */,
@@ -3019,7 +3012,7 @@
C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */, C471E84528F9BB650021E251 /* App+GlobalHotkey.swift in Sources */,
C4513F922B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */, C4513F922B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */, C471E84628F9BB650021E251 /* InterAppHandler.swift in Sources */,
032DAC2B2E8BEB5B0018E01C /* RealApi.swift in Sources */, 032DAC2B2E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
C471E84728F9BB650021E251 /* Startup.swift in Sources */, C471E84728F9BB650021E251 /* Startup.swift in Sources */,
C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */, C471E84828F9BB650021E251 /* EnvironmentCheck.swift in Sources */,
C471E84A28F9BB650021E251 /* AppVersion.swift in Sources */, C471E84A28F9BB650021E251 /* AppVersion.swift in Sources */,
@@ -3067,7 +3060,6 @@
C4463FCE29804BCB007B93D5 /* RCFile.swift in Sources */, C4463FCE29804BCB007B93D5 /* RCFile.swift in Sources */,
C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */, C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */,
C471E86528F9BB650021E251 /* WarningManager.swift in Sources */, C471E86528F9BB650021E251 /* WarningManager.swift in Sources */,
039C29142E8AA163007F5FAB /* ActiveApi.swift in Sources */,
C471E86628F9BB650021E251 /* PhpDoctorWindowController.swift in Sources */, C471E86628F9BB650021E251 /* PhpDoctorWindowController.swift in Sources */,
C471E86728F9BB650021E251 /* OnboardingWindowController.swift in Sources */, C471E86728F9BB650021E251 /* OnboardingWindowController.swift in Sources */,
C471E86828F9BB650021E251 /* PreferencesWindowController.swift in Sources */, C471E86828F9BB650021E251 /* PreferencesWindowController.swift in Sources */,
@@ -3184,7 +3176,7 @@
C489E0BD2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */, C489E0BD2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */, C45E2A77291992DA005C7CFD /* FeatureTestCase.swift in Sources */,
C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */, C471E82028F9BB290021E251 /* NginxConfigurationFile.swift in Sources */,
032DAC2D2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */, 032DAC2D2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
C4513F902B13E2E6001AD760 /* PhpExtensionManagerWindowController.swift in Sources */, C4513F902B13E2E6001AD760 /* PhpExtensionManagerWindowController.swift in Sources */,
C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */, C471E81528F9BAE80021E251 /* ArrayExtension.swift in Sources */,
C471E7DA28F9BA8F0021E251 /* TestableCommand.swift in Sources */, C471E7DA28F9BA8F0021E251 /* TestableCommand.swift in Sources */,
@@ -3260,7 +3252,7 @@
C471E8B528F9BB8F0021E251 /* MainMenu+FixMyValet.swift in Sources */, C471E8B528F9BB8F0021E251 /* MainMenu+FixMyValet.swift in Sources */,
C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */, C471E8B628F9BB8F0021E251 /* MainMenu+Actions.swift in Sources */,
C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */, C471E8B728F9BB8F0021E251 /* StatusMenu.swift in Sources */,
032DAC302E8BEB6B0018E01C /* ApiProtocol.swift in Sources */, 032DAC302E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */, C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */,
036C390C2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */, 036C390C2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */,
C471E8B928F9BB8F0021E251 /* DomainListCellProtocol.swift in Sources */, C471E8B928F9BB8F0021E251 /* DomainListCellProtocol.swift in Sources */,
@@ -3298,7 +3290,7 @@
C471E8CD28F9BB8F0021E251 /* PreferencesVC.swift in Sources */, C471E8CD28F9BB8F0021E251 /* PreferencesVC.swift in Sources */,
C471E8CE28F9BB8F0021E251 /* PreferenceName.swift in Sources */, C471E8CE28F9BB8F0021E251 /* PreferenceName.swift in Sources */,
C471E8CF28F9BB8F0021E251 /* Preferences.swift in Sources */, C471E8CF28F9BB8F0021E251 /* Preferences.swift in Sources */,
039C291B2E8AA314007F5FAB /* TestableApi.swift in Sources */, 039C291B2E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
03CC1FF62E3D23130050FC18 /* ZshRunCommand.swift in Sources */, 03CC1FF62E3D23130050FC18 /* ZshRunCommand.swift in Sources */,
C4415E902B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */, C4415E902B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C4E2E84D28FC1E70003B070C /* DataExtension.swift in Sources */, C4E2E84D28FC1E70003B070C /* DataExtension.swift in Sources */,
@@ -3307,7 +3299,6 @@
C471E8D228F9BB8F0021E251 /* Stats.swift in Sources */, C471E8D228F9BB8F0021E251 /* Stats.swift in Sources */,
C471E8D328F9BB8F0021E251 /* GlobalKeybindPreference.swift in Sources */, C471E8D328F9BB8F0021E251 /* GlobalKeybindPreference.swift in Sources */,
03C099462EA15C8E00B76D43 /* Container+Real.swift in Sources */, 03C099462EA15C8E00B76D43 /* Container+Real.swift in Sources */,
039C29152E8AA163007F5FAB /* ActiveApi.swift in Sources */,
C471E8D528F9BB8F0021E251 /* CheckboxPreferenceView.swift in Sources */, C471E8D528F9BB8F0021E251 /* CheckboxPreferenceView.swift in Sources */,
C471E8D728F9BB8F0021E251 /* SelectPreferenceView.swift in Sources */, C471E8D728F9BB8F0021E251 /* SelectPreferenceView.swift in Sources */,
03C29A7A2EC88E3100FBA25E /* ValetServicesDataManager.swift in Sources */, 03C29A7A2EC88E3100FBA25E /* ValetServicesDataManager.swift in Sources */,
@@ -3378,7 +3369,7 @@
C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */, C471E7F828F9BACB0021E251 /* InternalSwitcher.swift in Sources */,
C471E82328F9BB2E0021E251 /* ComposerJson.swift in Sources */, C471E82328F9BB2E0021E251 /* ComposerJson.swift in Sources */,
C471E82128F9BB2E0021E251 /* ProjectTypeDetection.swift in Sources */, C471E82128F9BB2E0021E251 /* ProjectTypeDetection.swift in Sources */,
032DAC282E8BEB5B0018E01C /* RealApi.swift in Sources */, 032DAC282E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */, C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */,
C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */, C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */,
C4D3660E29113F20006BD146 /* System.swift in Sources */, C4D3660E29113F20006BD146 /* System.swift in Sources */,
@@ -3457,7 +3448,7 @@
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */, 54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
C415D3B82770F294005EF286 /* Actions.swift in Sources */, C415D3B82770F294005EF286 /* Actions.swift in Sources */,
54B48B60275F66AE006D90C5 /* Application.swift in Sources */, 54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
039C291A2E8AA314007F5FAB /* TestableApi.swift in Sources */, 039C291A2E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */, C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */,
03C099472EA15C8E00B76D43 /* Container+Real.swift in Sources */, 03C099472EA15C8E00B76D43 /* Container+Real.swift in Sources */,
C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */, C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */,
@@ -3519,7 +3510,7 @@
C456A0C72AA614BD0080144F /* PhpPreference.swift in Sources */, C456A0C72AA614BD0080144F /* PhpPreference.swift in Sources */,
C42106672AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */, C42106672AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C46DC7A52C7B5BC900F19D17 /* Favorites.swift in Sources */, C46DC7A52C7B5BC900F19D17 /* Favorites.swift in Sources */,
032DAC2F2E8BEB6B0018E01C /* ApiProtocol.swift in Sources */, 032DAC2F2E8BEB6B0018E01C /* WebApiProtocol.swift in Sources */,
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */, C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */, C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */,
54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */, 54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
@@ -3528,7 +3519,7 @@
C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */, C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */,
031F24832EA1071A00CFB8D9 /* Container+Fake.swift in Sources */, 031F24832EA1071A00CFB8D9 /* Container+Fake.swift in Sources */,
C40D725B2A018ACC0054A067 /* BusyStatus.swift in Sources */, C40D725B2A018ACC0054A067 /* BusyStatus.swift in Sources */,
032DAC292E8BEB5B0018E01C /* RealApi.swift in Sources */, 032DAC292E8BEB5B0018E01C /* RealWebApi.swift in Sources */,
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */, C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */,
C40D72602A018AE30054A067 /* BrewFormula+UI.swift in Sources */, C40D72602A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */, C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */,
@@ -3622,7 +3613,6 @@
C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */, C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */,
C4BF56AC2949381100379603 /* FakeValetInteractor.swift in Sources */, C4BF56AC2949381100379603 /* FakeValetInteractor.swift in Sources */,
C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */, C471E79428F9B23B0021E251 /* FileSystemProtocol.swift in Sources */,
039C29162E8AA163007F5FAB /* ActiveApi.swift in Sources */,
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */, C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */,
C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */, C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */,
03D846252EB6344E006EFE3C /* DomainListVC+Window.swift in Sources */, 03D846252EB6344E006EFE3C /* DomainListVC+Window.swift in Sources */,

View File

@@ -1,25 +0,0 @@
//
// ActiveApi.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 29/09/2025.
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
var Api: ApiProtocol {
return ActiveApi.shared
}
class ActiveApi {
static var shared: ApiProtocol = RealApi()
public static func useTestable(_ responses: [URL: FakeApiResponse]) {
Self.shared = TestableApi(responses: responses)
}
public static func useReal() {
Self.shared = RealApi()
}
}

View File

@@ -6,4 +6,10 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
class RealApi: ApiProtocol {} class RealWebApi: WebApiProtocol {
var container: Container
init(container: Container) {
self.container = container
}
}

View File

@@ -1,5 +1,5 @@
// //
// TestableApi.swift // TestableWebApi.swift
// PHP Monitor // PHP Monitor
// //
// Created by Nico Verbruggen on 29/09/2025. // Created by Nico Verbruggen on 29/09/2025.
@@ -8,10 +8,10 @@
import Foundation import Foundation
class TestableApi: ApiProtocol { class TestableWebApi: WebApiProtocol {
private var fakeResponses: [URL: FakeApiResponse] = [:] private var fakeResponses: [URL: FakeWebApiResponse] = [:]
init(responses: [URL: FakeApiResponse]) { init(responses: [URL: FakeWebApiResponse]) {
self.fakeResponses = responses self.fakeResponses = responses
} }
@@ -19,12 +19,12 @@ class TestableApi: ApiProtocol {
return fakeResponses.keys.contains(url) return fakeResponses.keys.contains(url)
} }
public func getResponse(for url: URL) -> FakeApiResponse { public func getResponse(for url: URL) -> FakeWebApiResponse {
return fakeResponses[url]! return fakeResponses[url]!
} }
} }
struct FakeApiResponse { struct FakeWebApiResponse {
let statusCode: Int let statusCode: Int
let headers: [String: String] let headers: [String: String]
let data: Data? let data: Data?

View File

@@ -6,6 +6,6 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
protocol ApiProtocol { protocol WebApiProtocol {
} }

View File

@@ -13,6 +13,7 @@ class RealShell: ShellProtocol {
init(container: Container) { init(container: Container) {
self.container = container self.container = container
self.PATH = RealShell.getPath()
} }
/** /**
@@ -25,7 +26,7 @@ class RealShell: ShellProtocol {
For some commands, we need to know what's in the user's PATH. For some commands, we need to know what's in the user's PATH.
The entire PATH is retrieved here, so we can set the PATH in our own terminal as necessary. The entire PATH is retrieved here, so we can set the PATH in our own terminal as necessary.
*/ */
private(set) var PATH: String = { return RealShell.getPath() }() private(set) var PATH: String
/** /**
Exports are additional environment variables set by the user via the custom configuration. Exports are additional environment variables set by the user via the custom configuration.
@@ -269,8 +270,9 @@ class RealShell: ShellProtocol {
}) })
} }
func reload() { func reloadEnvPath() {
container.shell = RealShell(container: container) // Instead of replacing the entire shell instance, we simply re-fetch the PATH
self.PATH = RealShell.getPath()
} }
} }

View File

@@ -58,9 +58,9 @@ protocol ShellProtocol {
) async throws -> (Process, ShellOutput) ) async throws -> (Process, ShellOutput)
/** /**
Reloads the shell instance, which also reloads the PATH. Reloads the PATH.
*/ */
func reload() func reloadEnvPath()
} }
enum ShellStream: Codable { enum ShellStream: Codable {

View File

@@ -65,7 +65,7 @@ public class TestableShell: ShellProtocol {
return (Process(), output) return (Process(), output)
} }
func reload() { func reloadEnvPath() {
// does nothing // does nothing
} }
} }

View File

@@ -6,33 +6,9 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation
extension Container { extension Container {
/**
Manually specify what overrides need to be active for the container.
*/
private func overrideFake(
shellExpectations: [String: BatchFakeShellOutput] = [:],
fileSystemFiles: [String: FakeFile] = [:],
commands: [String: String] = [:]
) {
self.shell = TestableShell(expectations: shellExpectations)
self.filesystem = TestableFileSystem(files: fileSystemFiles)
self.command = TestableCommand(commands: commands)
}
/**
Use a `TestableConfiguration` as the basis for shell, filesystem and more.
This is used for testing scenarios to avoid needing to have a specific system configuration.
Ideal for feature or UI tests, where a complete "computer configuration" needs to be mimicked.
*/
public func overrideWith(config: TestableConfiguration) {
self.overrideFake(
shellExpectations: config.shellOutput,
fileSystemFiles: config.filesystem,
commands: config.commandOutput
)
}
/** /**
Create a new DI `Container` with fake shell responses, filesystem structure and given commands. Create a new DI `Container` with fake shell responses, filesystem structure and given commands.
Ideal for testing without a complex TestableConfiguration, so great for unit tests that Ideal for testing without a complex TestableConfiguration, so great for unit tests that
@@ -41,19 +17,21 @@ extension Container {
public static func fake( public static func fake(
shell: [String: BatchFakeShellOutput] = [:], shell: [String: BatchFakeShellOutput] = [:],
files: [String: FakeFile] = [:], files: [String: FakeFile] = [:],
commands: [String: String] = [:] commands: [String: String] = [:],
apiResponses: [URL: FakeWebApiResponse] = [:]
) -> Container { ) -> Container {
// Create a new container // Create a new container
let container = Container() let container = Container()
// Fill the container with production (real) components // Fill the container with production (real) components
container.prepare() container.bind()
// Replace the key ones with fake ones, so we don't touch the tester's OS, filesystem, etc. // Replace the key ones with fake ones, so we don't touch the tester's OS, filesystem, etc.
container.overrideFake( container.overrideFake(
shellExpectations: shell, shellExpectations: shell,
fileSystemFiles: files, fileSystemFiles: files,
commands: commands commands: commands,
fakeApiResponses: apiResponses
) )
// Return the newly created container // Return the newly created container

View File

@@ -9,7 +9,7 @@
extension Container { extension Container {
public static func real() -> Container { public static func real() -> Container {
let container = Container() let container = Container()
container.prepare() container.bind()
return container return container
} }
} }

View File

@@ -6,22 +6,34 @@
// Copyright © 2025 Nico Verbruggen. All rights reserved. // Copyright © 2025 Nico Verbruggen. All rights reserved.
// //
import Foundation
class Container { class Container {
// MARK: - Variables
// Primary // Primary
var shell: ShellProtocol! private(set) var shell: ShellProtocol!
var filesystem: FileSystemProtocol! private(set) var filesystem: FileSystemProtocol!
var command: CommandProtocol! private(set) var command: CommandProtocol!
var paths: Paths! private(set) var paths: Paths!
private(set) var webApi: WebApiProtocol!
// Secondary (uses primary instances above) // Secondary (uses primary instances above)
var preferences: Preferences! private(set) var preferences: Preferences!
var phpEnvs: PhpEnvironments! private(set) var phpEnvs: PhpEnvironments!
var favorites: Favorites! private(set) var favorites: Favorites!
var warningManager: WarningManager! private(set) var warningManager: WarningManager!
// Track initial preparation step
private var bound: Bool = false
// MARK: - Initializers
/// ///
/// The initializer is empty. You must call `prepare` to enable the container. /// The initializer is empty. You must call `bind` to enable the container.
/// To avoid issues with unsafe access, the actual objects are set in `prepare`. ///
/// To avoid issues with unsafe access, the actual objects are set in `bind`.
///
/// `self` is not available in this constructor, after all. The alternative /// `self` is not available in this constructor, after all. The alternative
/// is to use lazy variables here, but I don't think it's that clean, especially /// is to use lazy variables here, but I don't think it's that clean, especially
/// given the other initializers available via the extensions. /// given the other initializers available via the extensions.
@@ -29,16 +41,28 @@ class Container {
init() {} init() {}
/// ///
/// Creates new instances belonging to the container, while referencing /// Creates new instances of all elements belonging to the `Container`, while referencing
/// the container itself and passing the reference on to each component that needs it. /// the `Container` itself and passing the reference on to each component that needs it.
/// ///
public func prepare() { /// You can only call this method once. Running it again will crash with `fatalError`,
/// because it would cause all sorts of issues if individual DI elements are swapped out
/// without proper deinitialization.
///
/// (Swapping instances for specific dependencies can be introduced later with dedicated
/// methods if it ever becomes truly necessary.)
///
public func bind() {
if self.bound {
fatalError("You cannot call `bind` on a Container more than once.")
}
// These are the most basic building blocks. We need these before // These are the most basic building blocks. We need these before
// any of the other classes can be initialized! // any of the other classes can be initialized!
self.shell = RealShell(container: self) self.shell = RealShell(container: self)
self.filesystem = RealFileSystem(container: self) self.filesystem = RealFileSystem(container: self)
self.command = RealCommand() self.command = RealCommand()
self.paths = Paths(container: self) self.paths = Paths(container: self)
self.webApi = RealWebApi(container: self)
// Please note that the order in which these are initialized, matters! // Please note that the order in which these are initialized, matters!
// For example, preferences leverages the Paths instance, so don't just // For example, preferences leverages the Paths instance, so don't just
@@ -47,5 +71,39 @@ class Container {
self.phpEnvs = PhpEnvironments(container: self) self.phpEnvs = PhpEnvironments(container: self)
self.favorites = Favorites() self.favorites = Favorites()
self.warningManager = WarningManager(container: self) self.warningManager = WarningManager(container: self)
// At this point, our container has been bound.
self.bound = true
}
/**
Manually specify what testable overrides need to be active for the `Container`.
Only used for testing purposes, either via `TestableConfiguration` or for
explicit initialization of a fake Container instance.
*/
public func overrideFake(
shellExpectations: [String: BatchFakeShellOutput] = [:],
fileSystemFiles: [String: FakeFile] = [:],
commands: [String: String] = [:],
fakeApiResponses: [URL: FakeWebApiResponse] = [:]
) {
self.shell = TestableShell(expectations: shellExpectations)
self.filesystem = TestableFileSystem(files: fileSystemFiles)
self.command = TestableCommand(commands: commands)
self.webApi = TestableWebApi(responses: fakeApiResponses)
}
/**
Use a `TestableConfiguration` as the basis for shell, filesystem and more.
This is used for testing scenarios to avoid needing to have a specific system configuration.
Ideal for feature or UI tests, where a complete "computer configuration" needs to be mimicked.
*/
public func overrideWith(config: TestableConfiguration) {
self.overrideFake(
shellExpectations: config.shellOutput,
fileSystemFiles: config.filesystem,
commands: config.commandOutput
)
} }
} }

View File

@@ -49,7 +49,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
override init() { override init() {
// Prepare the container with the defaults // Prepare the container with the defaults
self.state = App.shared self.state = App.shared
self.state.container.prepare() self.state.container.bind()
#if DEBUG #if DEBUG
logger.verbosity = .performance logger.verbosity = .performance

View File

@@ -60,7 +60,7 @@ class WarningManager: ObservableObject {
Checks the user's environment and checks if any special warnings apply. Checks the user's environment and checks if any special warnings apply.
*/ */
func checkEnvironment() async { func checkEnvironment() async {
container.shell.reload() container.shell.reloadEnvPath()
await BrewDiagnostics.shared.loadInstalledTaps() await BrewDiagnostics.shared.loadInstalledTaps()

View File

@@ -10,18 +10,26 @@ import Testing
import Foundation import Foundation
struct TestableApiTest { struct TestableApiTest {
@Test func createFakeApi() { private var container: Container
let api = TestableApi(responses: [
url("https://api.phpmon.test"): FakeApiResponse( init() throws {
self.container = Container.fake(apiResponses: [
url("https://api.phpmon.test"): FakeWebApiResponse(
statusCode: 200, statusCode: 200,
headers: [:], headers: [:],
text: "{\"success\": true}" text: "{\"success\": true}"
) )
]) ])
}
#expect(api.hasResponse(for: url("https://api.phpmon.test")) == true) var WebApi: TestableWebApi {
return container.webApi as! TestableWebApi
}
let response = api.getResponse(for: url("https://api.phpmon.test")) @Test func createFakeApi() {
#expect(WebApi.hasResponse(for: url("https://api.phpmon.test")) == true)
let response = WebApi.getResponse(for: url("https://api.phpmon.test"))
#expect(response.statusCode == 200) #expect(response.statusCode == 200)
#expect(response.text.contains("success")) #expect(response.text.contains("success"))

View File

@@ -15,7 +15,7 @@ struct RealFileSystemTest {
init() throws { init() throws {
let container = Container() let container = Container()
container.prepare() container.bind()
filesystem = container.filesystem filesystem = container.filesystem
} }

View File

@@ -11,7 +11,8 @@ import Foundation
@Suite(.serialized) @Suite(.serialized)
struct TestableFileSystemTest { struct TestableFileSystemTest {
var container: Container private var container: Container
init() throws { init() throws {
container = Container.fake(files: [ container = Container.fake(files: [
"/home/user/bin/foo": .fake(.binary), "/home/user/bin/foo": .fake(.binary),