cypress 14.5.1 → 14.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  import 'zone.js';
9
9
  import 'zone.js/testing';
10
10
  import { CommonModule } from '@angular/common';
11
- import { assertInInjectionContext, inject, Injector, effect, untracked, DestroyRef, Injectable, Component, EventEmitter, ErrorHandler, SimpleChange, signal } from '@angular/core';
11
+ import { assertInInjectionContext, inject, Injector, effect, untracked, DestroyRef, Injectable, Component, EventEmitter, ErrorHandler, SimpleChange } from '@angular/core';
12
12
  import { getTestBed, TestComponentRenderer, TestBed } from '@angular/core/testing';
13
13
  import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
14
14
 
@@ -1046,55 +1046,30 @@ function isModelSignal(prop) {
1046
1046
  function isWritableSignal(prop) {
1047
1047
  return isSignal(prop) && typeof prop === 'function' && typeof prop.set === 'function';
1048
1048
  }
1049
- function convertPropertyToSignalIfApplicable(propValue, componentValue, injector) {
1050
- const isComponentValueAnInputSignal = isInputSignal(componentValue);
1051
- const isComponentValueAModelSignal = isModelSignal(componentValue);
1052
- let convertedValueIfApplicable = propValue;
1053
- // If the component has the property defined as an InputSignal, we need to detect whether a non signal value or not was passed into the component as a prop
1054
- // and attempt to merge the value in correctly.
1055
- // We don't want to expose the primitive created signal as it should really be one-way binding from within the component.
1056
- // However, to make CT testing easier, a user can technically pass in a signal to an input component and assert on the signal itself and pass in updates
1057
- // down to the component as 1 way binding is supported by the test harness
1058
- if (isComponentValueAnInputSignal) {
1059
- const isPassedInValueNotASignal = !isSignal(propValue);
1060
- if (isPassedInValueNotASignal) {
1061
- // Input signals require an injection context to set initial values.
1062
- // Because of this, we cannot create them outside the scope of the component.
1063
- // Options for input signals also don't allow the passing of an injection contexts, so in order to work around this,
1064
- // we convert the non signal input passed into the input to a writable signal
1065
- convertedValueIfApplicable = signal(propValue);
1066
- }
1067
- // If the component has the property defined as a ModelSignal, we need to detect whether a signal value or not was passed into the component as a prop.
1068
- // If a non signal property is passed into the component model (primitive, object, array, etc), we need to set the model to that value and propagate changes of that model through the output spy.
1069
- // Since the non signal type likely lives outside the context of Angular, the non signal type will NOT be updated outside of this context. Instead, the output spy will allow you
1070
- // to see this change.
1071
- // If the value passed into the property is in fact a signal, we need to set up two-way binding between the signals to make sure changes from one propagate to the other.
1049
+ function registerSignalEventsIfNeeded(propKey, propValue, componentValue, injector, fixture) {
1050
+ const isPropValueASignal = isSignal(propValue);
1051
+ if (isPropValueASignal) {
1052
+ // propValue -> componentValue
1053
+ const convertedToObservable = toObservable(propValue, {
1054
+ injector,
1055
+ });
1056
+ // push the subscription into an array to be cleaned up at the end of the test
1057
+ // to prevent a memory leak
1058
+ activeInternalSubscriptions.push(convertedToObservable.subscribe((value) => {
1059
+ // keep the component up to date as prop signal changes
1060
+ fixture.componentRef.setInput(propKey, value);
1061
+ }));
1072
1062
  }
1073
- else if (isComponentValueAModelSignal) {
1074
- const isPassedInValueLikelyARegularSignal = isWritableSignal(propValue);
1075
- // if the value passed into the component is a signal, set up two-way binding
1076
- if (isPassedInValueLikelyARegularSignal) {
1077
- // update the passed in value with the models updates
1078
- componentValue.subscribe((value) => {
1079
- propValue.set(value);
1080
- });
1081
- // update the model signal with the properties updates
1082
- const convertedToObservable = toObservable(propValue, {
1083
- injector,
1084
- });
1085
- // push the subscription into an array to be cleaned up at the end of the test
1086
- // to prevent a memory leak
1087
- activeInternalSubscriptions.push(convertedToObservable.subscribe((value) => {
1088
- componentValue.set(value);
1089
- }));
1090
- }
1091
- else {
1092
- // it's a non signal type, set it as we only need to handle updating the model signal and emit changes on this through the output spy.
1093
- componentValue.set(propValue);
1094
- convertedValueIfApplicable = componentValue;
1095
- }
1063
+ const isComponentValueAModelSignal = isModelSignal(componentValue);
1064
+ if (isPropValueASignal && isComponentValueAModelSignal) {
1065
+ // propValue <- componentValue
1066
+ const modelChanged$ = toObservable(componentValue, {
1067
+ injector,
1068
+ });
1069
+ activeInternalSubscriptions.push(modelChanged$.subscribe((value) => {
1070
+ propValue.set(value);
1071
+ }));
1096
1072
  }
1097
- return convertedValueIfApplicable;
1098
1073
  }
1099
1074
  // In the case of signals, if we need to create an output spy, we need to check first whether or not a user has one defined first or has it created through
1100
1075
  // autoSpyOutputs. If so, we need to subscribe to the writable signal to push updates into the event emitter. We do NOT observe input signals and output spies will not
@@ -1103,6 +1078,11 @@ function detectAndRegisterOutputSpyToSignal(config, component, key, injector) {
1103
1078
  if (config.componentProperties) {
1104
1079
  const expectedChangeKey = `${key}Change`;
1105
1080
  let changeKeyIfExists = !!Object.keys(config.componentProperties).find((componentKey) => componentKey === expectedChangeKey);
1081
+ if (changeKeyIfExists) {
1082
+ component[expectedChangeKey] =
1083
+ // @ts-expect-error
1084
+ config.componentProperties[expectedChangeKey];
1085
+ }
1106
1086
  // since spies do NOT make change handlers by default, similar to the Output() decorator, we need to create the spy and subscribe to the signal
1107
1087
  if (!changeKeyIfExists && config.autoSpyOutputs) {
1108
1088
  component[expectedChangeKey] = createOutputSpy(`${expectedChangeKey}Spy`);
@@ -1135,21 +1115,32 @@ function setupComponent(config, fixture) {
1135
1115
  let component = fixture.componentInstance;
1136
1116
  const injector = fixture.componentRef.injector;
1137
1117
  if (config === null || config === void 0 ? void 0 : config.componentProperties) {
1138
- // convert primitives to signals if passed in type is a primitive but expected type is signal
1139
- // a bit of magic. need to move to another function
1140
- Object.keys(component).forEach((key) => {
1118
+ if (component instanceof WrapperComponent) {
1119
+ component = Object.assign(component, config.componentProperties);
1120
+ }
1121
+ getComponentInputs(fixture.componentRef.componentType).forEach((key) => {
1141
1122
  var _a;
1142
1123
  // only assign props if they are passed into the component
1143
1124
  if ((_a = config === null || config === void 0 ? void 0 : config.componentProperties) === null || _a === void 0 ? void 0 : _a.hasOwnProperty(key)) {
1144
1125
  // @ts-expect-error
1145
1126
  const passedInValue = config === null || config === void 0 ? void 0 : config.componentProperties[key];
1146
- const componentValue = component[key];
1147
- // @ts-expect-error
1148
- config.componentProperties[key] = convertPropertyToSignalIfApplicable(passedInValue, componentValue, injector);
1127
+ registerSignalEventsIfNeeded(key, passedInValue, component[key], injector, fixture);
1149
1128
  detectAndRegisterOutputSpyToSignal(config, component, key, injector);
1129
+ fixture.componentRef.setInput(key, isSignal(passedInValue) ? passedInValue() : passedInValue);
1130
+ }
1131
+ });
1132
+ getComponentOutputs(fixture.componentRef.componentType).forEach((key) => {
1133
+ var _a;
1134
+ const property = component[key];
1135
+ if (property instanceof EventEmitter) {
1136
+ // only assign props if they are passed into the component
1137
+ if ((_a = config === null || config === void 0 ? void 0 : config.componentProperties) === null || _a === void 0 ? void 0 : _a.hasOwnProperty(key)) {
1138
+ // @ts-expect-error
1139
+ const passedInValue = config === null || config === void 0 ? void 0 : config.componentProperties[key];
1140
+ component[key] = passedInValue;
1141
+ }
1150
1142
  }
1151
1143
  });
1152
- component = Object.assign(component, config.componentProperties);
1153
1144
  }
1154
1145
  if (config.autoSpyOutputs) {
1155
1146
  Object.keys(component).forEach((key) => {
@@ -1173,6 +1164,24 @@ function setupComponent(config, fixture) {
1173
1164
  }
1174
1165
  }
1175
1166
  }
1167
+ /**
1168
+ * Gets the input properties of a component - cannot rely on Object.keys() because inclusion of optional properties depends on useDefineForClassFields=true
1169
+ * Since Angular 15, useDefineForClassFields=false
1170
+ * @param componentType
1171
+ * @returns array of input property names
1172
+ */
1173
+ function getComponentInputs(componentType) {
1174
+ var _a;
1175
+ // Access Angular's metadata to get input properties
1176
+ const propMetadata = ((_a = componentType.ɵcmp) === null || _a === void 0 ? void 0 : _a.inputs) || {};
1177
+ return Object.keys(propMetadata);
1178
+ }
1179
+ function getComponentOutputs(componentType) {
1180
+ var _a;
1181
+ // Access Angular's metadata to get output properties
1182
+ const propMetadata = ((_a = componentType.ɵcmp) === null || _a === void 0 ? void 0 : _a.outputs) || {};
1183
+ return Object.keys(propMetadata);
1184
+ }
1176
1185
  /**
1177
1186
  * Mounts an Angular component inside Cypress browser
1178
1187
  *
@@ -8,7 +8,7 @@
8
8
  import 'zone.js';
9
9
  import 'zone.js/testing';
10
10
  import { CommonModule } from '@angular/common';
11
- import { assertInInjectionContext, inject, Injector, effect, untracked, DestroyRef, Injectable, Component, EventEmitter, ErrorHandler, SimpleChange, signal } from '@angular/core';
11
+ import { assertInInjectionContext, inject, Injector, effect, untracked, DestroyRef, Injectable, Component, EventEmitter, ErrorHandler, SimpleChange } from '@angular/core';
12
12
  import { getTestBed, TestComponentRenderer, TestBed } from '@angular/core/testing';
13
13
  import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
14
14
 
@@ -1046,55 +1046,30 @@ function isModelSignal(prop) {
1046
1046
  function isWritableSignal(prop) {
1047
1047
  return isSignal(prop) && typeof prop === 'function' && typeof prop.set === 'function';
1048
1048
  }
1049
- function convertPropertyToSignalIfApplicable(propValue, componentValue, injector) {
1050
- const isComponentValueAnInputSignal = isInputSignal(componentValue);
1051
- const isComponentValueAModelSignal = isModelSignal(componentValue);
1052
- let convertedValueIfApplicable = propValue;
1053
- // If the component has the property defined as an InputSignal, we need to detect whether a non signal value or not was passed into the component as a prop
1054
- // and attempt to merge the value in correctly.
1055
- // We don't want to expose the primitive created signal as it should really be one-way binding from within the component.
1056
- // However, to make CT testing easier, a user can technically pass in a signal to an input component and assert on the signal itself and pass in updates
1057
- // down to the component as 1 way binding is supported by the test harness
1058
- if (isComponentValueAnInputSignal) {
1059
- const isPassedInValueNotASignal = !isSignal(propValue);
1060
- if (isPassedInValueNotASignal) {
1061
- // Input signals require an injection context to set initial values.
1062
- // Because of this, we cannot create them outside the scope of the component.
1063
- // Options for input signals also don't allow the passing of an injection contexts, so in order to work around this,
1064
- // we convert the non signal input passed into the input to a writable signal
1065
- convertedValueIfApplicable = signal(propValue);
1066
- }
1067
- // If the component has the property defined as a ModelSignal, we need to detect whether a signal value or not was passed into the component as a prop.
1068
- // If a non signal property is passed into the component model (primitive, object, array, etc), we need to set the model to that value and propagate changes of that model through the output spy.
1069
- // Since the non signal type likely lives outside the context of Angular, the non signal type will NOT be updated outside of this context. Instead, the output spy will allow you
1070
- // to see this change.
1071
- // If the value passed into the property is in fact a signal, we need to set up two-way binding between the signals to make sure changes from one propagate to the other.
1049
+ function registerSignalEventsIfNeeded(propKey, propValue, componentValue, injector, fixture) {
1050
+ const isPropValueASignal = isSignal(propValue);
1051
+ if (isPropValueASignal) {
1052
+ // propValue -> componentValue
1053
+ const convertedToObservable = toObservable(propValue, {
1054
+ injector,
1055
+ });
1056
+ // push the subscription into an array to be cleaned up at the end of the test
1057
+ // to prevent a memory leak
1058
+ activeInternalSubscriptions.push(convertedToObservable.subscribe((value) => {
1059
+ // keep the component up to date as prop signal changes
1060
+ fixture.componentRef.setInput(propKey, value);
1061
+ }));
1072
1062
  }
1073
- else if (isComponentValueAModelSignal) {
1074
- const isPassedInValueLikelyARegularSignal = isWritableSignal(propValue);
1075
- // if the value passed into the component is a signal, set up two-way binding
1076
- if (isPassedInValueLikelyARegularSignal) {
1077
- // update the passed in value with the models updates
1078
- componentValue.subscribe((value) => {
1079
- propValue.set(value);
1080
- });
1081
- // update the model signal with the properties updates
1082
- const convertedToObservable = toObservable(propValue, {
1083
- injector,
1084
- });
1085
- // push the subscription into an array to be cleaned up at the end of the test
1086
- // to prevent a memory leak
1087
- activeInternalSubscriptions.push(convertedToObservable.subscribe((value) => {
1088
- componentValue.set(value);
1089
- }));
1090
- }
1091
- else {
1092
- // it's a non signal type, set it as we only need to handle updating the model signal and emit changes on this through the output spy.
1093
- componentValue.set(propValue);
1094
- convertedValueIfApplicable = componentValue;
1095
- }
1063
+ const isComponentValueAModelSignal = isModelSignal(componentValue);
1064
+ if (isPropValueASignal && isComponentValueAModelSignal) {
1065
+ // propValue <- componentValue
1066
+ const modelChanged$ = toObservable(componentValue, {
1067
+ injector,
1068
+ });
1069
+ activeInternalSubscriptions.push(modelChanged$.subscribe((value) => {
1070
+ propValue.set(value);
1071
+ }));
1096
1072
  }
1097
- return convertedValueIfApplicable;
1098
1073
  }
1099
1074
  // In the case of signals, if we need to create an output spy, we need to check first whether or not a user has one defined first or has it created through
1100
1075
  // autoSpyOutputs. If so, we need to subscribe to the writable signal to push updates into the event emitter. We do NOT observe input signals and output spies will not
@@ -1103,6 +1078,11 @@ function detectAndRegisterOutputSpyToSignal(config, component, key, injector) {
1103
1078
  if (config.componentProperties) {
1104
1079
  const expectedChangeKey = `${key}Change`;
1105
1080
  let changeKeyIfExists = !!Object.keys(config.componentProperties).find((componentKey) => componentKey === expectedChangeKey);
1081
+ if (changeKeyIfExists) {
1082
+ component[expectedChangeKey] =
1083
+ // @ts-expect-error
1084
+ config.componentProperties[expectedChangeKey];
1085
+ }
1106
1086
  // since spies do NOT make change handlers by default, similar to the Output() decorator, we need to create the spy and subscribe to the signal
1107
1087
  if (!changeKeyIfExists && config.autoSpyOutputs) {
1108
1088
  component[expectedChangeKey] = createOutputSpy(`${expectedChangeKey}Spy`);
@@ -1135,21 +1115,32 @@ function setupComponent(config, fixture) {
1135
1115
  let component = fixture.componentInstance;
1136
1116
  const injector = fixture.componentRef.injector;
1137
1117
  if (config === null || config === void 0 ? void 0 : config.componentProperties) {
1138
- // convert primitives to signals if passed in type is a primitive but expected type is signal
1139
- // a bit of magic. need to move to another function
1140
- Object.keys(component).forEach((key) => {
1118
+ if (component instanceof WrapperComponent) {
1119
+ component = Object.assign(component, config.componentProperties);
1120
+ }
1121
+ getComponentInputs(fixture.componentRef.componentType).forEach((key) => {
1141
1122
  var _a;
1142
1123
  // only assign props if they are passed into the component
1143
1124
  if ((_a = config === null || config === void 0 ? void 0 : config.componentProperties) === null || _a === void 0 ? void 0 : _a.hasOwnProperty(key)) {
1144
1125
  // @ts-expect-error
1145
1126
  const passedInValue = config === null || config === void 0 ? void 0 : config.componentProperties[key];
1146
- const componentValue = component[key];
1147
- // @ts-expect-error
1148
- config.componentProperties[key] = convertPropertyToSignalIfApplicable(passedInValue, componentValue, injector);
1127
+ registerSignalEventsIfNeeded(key, passedInValue, component[key], injector, fixture);
1149
1128
  detectAndRegisterOutputSpyToSignal(config, component, key, injector);
1129
+ fixture.componentRef.setInput(key, isSignal(passedInValue) ? passedInValue() : passedInValue);
1130
+ }
1131
+ });
1132
+ getComponentOutputs(fixture.componentRef.componentType).forEach((key) => {
1133
+ var _a;
1134
+ const property = component[key];
1135
+ if (property instanceof EventEmitter) {
1136
+ // only assign props if they are passed into the component
1137
+ if ((_a = config === null || config === void 0 ? void 0 : config.componentProperties) === null || _a === void 0 ? void 0 : _a.hasOwnProperty(key)) {
1138
+ // @ts-expect-error
1139
+ const passedInValue = config === null || config === void 0 ? void 0 : config.componentProperties[key];
1140
+ component[key] = passedInValue;
1141
+ }
1150
1142
  }
1151
1143
  });
1152
- component = Object.assign(component, config.componentProperties);
1153
1144
  }
1154
1145
  if (config.autoSpyOutputs) {
1155
1146
  Object.keys(component).forEach((key) => {
@@ -1173,6 +1164,24 @@ function setupComponent(config, fixture) {
1173
1164
  }
1174
1165
  }
1175
1166
  }
1167
+ /**
1168
+ * Gets the input properties of a component - cannot rely on Object.keys() because inclusion of optional properties depends on useDefineForClassFields=true
1169
+ * Since Angular 15, useDefineForClassFields=false
1170
+ * @param componentType
1171
+ * @returns array of input property names
1172
+ */
1173
+ function getComponentInputs(componentType) {
1174
+ var _a;
1175
+ // Access Angular's metadata to get input properties
1176
+ const propMetadata = ((_a = componentType.ɵcmp) === null || _a === void 0 ? void 0 : _a.inputs) || {};
1177
+ return Object.keys(propMetadata);
1178
+ }
1179
+ function getComponentOutputs(componentType) {
1180
+ var _a;
1181
+ // Access Angular's metadata to get output properties
1182
+ const propMetadata = ((_a = componentType.ɵcmp) === null || _a === void 0 ? void 0 : _a.outputs) || {};
1183
+ return Object.keys(propMetadata);
1184
+ }
1176
1185
  /**
1177
1186
  * Mounts an Angular component inside Cypress browser
1178
1187
  *
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "cypress",
3
- "version": "14.5.1",
3
+ "version": "14.5.3",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "postinstall": "node index.js --exec install",
7
7
  "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";"
8
8
  },
9
9
  "dependencies": {
10
- "@cypress/request": "^3.0.8",
10
+ "@cypress/request": "^3.0.9",
11
11
  "@cypress/xvfb": "^1.2.4",
12
12
  "@types/sinonjs__fake-timers": "8.1.1",
13
13
  "@types/sizzle": "^2.3.2",
@@ -124,8 +124,8 @@
124
124
  },
125
125
  "buildInfo": {
126
126
  "commitBranch": "develop",
127
- "commitSha": "fa25461cb0ab04487f487aa5ee8d9b5e849a7de4",
128
- "commitDate": "2025-07-01T19:14:05.000Z",
127
+ "commitSha": "d1478d8961a69dc3c0304d3d3d85ef68fbb5e2eb",
128
+ "commitDate": "2025-07-24T19:13:32.000Z",
129
129
  "stable": true
130
130
  },
131
131
  "description": "Cypress is a next generation front end testing tool built for the modern web",