@planningcenter/tapestry-migration-cli 3.6.0 → 3.6.1-qa-1035.0

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.
Files changed (25) hide show
  1. package/dist/tapestry-react-shim.cjs +195 -11
  2. package/package.json +3 -3
  3. package/src/components/flex/index.test.ts +151 -0
  4. package/src/components/flex/index.ts +14 -0
  5. package/src/components/flex/transforms/auditSpreadProps.test.ts +93 -0
  6. package/src/components/flex/transforms/auditSpreadProps.ts +6 -0
  7. package/src/components/flex/transforms/axisToDirection.test.ts +15 -0
  8. package/src/components/flex/transforms/axisToDirection.ts +26 -0
  9. package/src/components/flex/transforms/convertStyleProps.test.ts +147 -0
  10. package/src/components/flex/transforms/convertStyleProps.ts +23 -0
  11. package/src/components/flex/transforms/cssFlexAliasesToProps.test.ts +202 -0
  12. package/src/components/flex/transforms/cssFlexAliasesToProps.ts +110 -0
  13. package/src/components/flex/transforms/flexPropToExpand.test.ts +156 -0
  14. package/src/components/flex/transforms/flexPropToExpand.ts +100 -0
  15. package/src/components/flex/transforms/mediaQueriesToResponsive.test.ts +714 -0
  16. package/src/components/flex/transforms/mediaQueriesToResponsive.ts +523 -0
  17. package/src/components/flex/transforms/paddingPropsToFlex.test.ts +252 -0
  18. package/src/components/flex/transforms/paddingPropsToFlex.ts +150 -0
  19. package/src/components/flex/transforms/setDefaultAxis.test.ts +12 -0
  20. package/src/components/flex/transforms/setDefaultAxis.ts +1 -0
  21. package/src/components/flex/transforms/unsupportedProps.test.ts +141 -0
  22. package/src/components/flex/transforms/unsupportedProps.ts +17 -0
  23. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +41 -0
  24. package/src/components/shared/transformFactories/stylePropTransformFactory.test.ts +47 -0
  25. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +16 -0
@@ -1,5 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ function _mergeNamespaces(n2, m2) {
4
+ for (var i2 = 0; i2 < m2.length; i2++) {
5
+ const e2 = m2[i2];
6
+ if (typeof e2 !== "string" && !Array.isArray(e2)) {
7
+ for (const k2 in e2) {
8
+ if (k2 !== "default" && !(k2 in n2)) {
9
+ const d2 = Object.getOwnPropertyDescriptor(e2, k2);
10
+ if (d2) {
11
+ Object.defineProperty(n2, k2, d2.get ? d2 : {
12
+ enumerable: true,
13
+ get: () => e2[k2]
14
+ });
15
+ }
16
+ }
17
+ }
18
+ }
19
+ }
20
+ return Object.freeze(Object.defineProperty(n2, Symbol.toStringTag, { value: "Module" }));
21
+ }
3
22
  const bellMuted = "M6.13,2.07l-1.04,-1.03c0.67,-0.48 1.54,-1.04 3.08,-1.04c1.54,0.01 3.01,0.62 4.09,1.68c1.07529,1.04811 1.67813,2.48843 1.67,3.99v4.27l-1.54,-1.55v-2.71c0,-0.54 -0.11,-1.07 -0.32,-1.56c-0.21,-0.5 -0.52,-0.95 -0.92,-1.33c-0.39,-0.38 -0.86,-0.68 -1.37,-0.89c-0.51,-0.21 -1.06,-0.31 -1.62,-0.31c-0.70992,0.00342 -1.40896,0.17475 -2.04,0.5zM16,14.59l-1.4,1.41l-1.74,-1.74h-2.15c-0.2,0.48 -0.54,0.9 -0.99,1.19c-0.45,0.29 -0.97,0.45 -1.51,0.45c-0.54,0 -1.06,-0.16 -1.51,-0.45c-0.45,-0.29 -0.79,-0.71 -0.99,-1.19h-4.08c-0.31,0 -0.62,-0.09 -0.88,-0.25c-0.26165,-0.16091 -0.4676,-0.39827 -0.59,-0.68c-0.13,-0.28 -0.18,-0.59 -0.14,-0.89c0.04,-0.3 0.17,-0.59 0.38,-0.82l2.06,-2.38v-3.58c0,-0.45 0.16,-1.2 0.25,-1.59l-2.11,-2.1l1.4,-1.41zM11.34,12.73l-7.34,-7.36c0,0.1 -0.02,0.21 -0.02,0.29v3.85c0,0.19 -0.07,0.38 -0.19,0.52l-2.23,2.58l0.07,0.12z";
4
23
  const bell = "M15.233,11.506c0.38455,0.4554 0.46962,1.09267 0.218,1.633c-0.23682,0.53936 -0.77094,0.88694 -1.36,0.885h-3.782c-0.377,0.9449 -1.29167,1.56474 -2.309,1.56474c-1.01733,0 -1.932,-0.61985 -2.309,-1.56474h-3.78c-0.59015,0.00313 -1.12575,-0.34463 -1.363,-0.885c-0.25178,-0.54079 -0.16629,-1.1786 0.219,-1.634l1.905,-2.271v-3.417c-0.01886,-2.9628 2.36425,-5.38169 5.327,-5.407c2.9633,0.02476 5.3473,2.44365 5.329,5.407v3.417l1.905,2.272";
5
24
  const bill = "M15.805,2.176c0.12423,0.10317 0.19577,0.25652 0.195,0.418v10.795c0.00021,0.08218 -0.01647,0.16353 -0.049,0.239c-0.11474,0.23112 -0.352,0.37581 -0.61,0.372h-14.665c-0.177,0.003 -0.349,-0.06 -0.482,-0.177c-0.12591,-0.10868 -0.19699,-0.2677 -0.194,-0.434v-10.795c-0.00102,-0.16132 0.07014,-0.31464 0.194,-0.418c0.1326,-0.11756 0.30485,-0.18046 0.482,-0.176h14.664c0.17184,-0.00314 0.33831,0.05986 0.465,0.176M14.798,5.565c-0.11067,0.022 -0.22267,0.03367 -0.336,0.035c-0.64408,-0.00048 -1.26271,-0.25152 -1.725,-0.7c-0.461,-0.447 -0.73,-1.057 -0.75,-1.7h-7.947c-0.0426,1.33678 -1.13754,2.39898 -2.475,2.401c-0.122,-0.00067 -0.243,-0.01333 -0.363,-0.038v4.876c0.11959,-0.0237 0.24109,-0.03642 0.363,-0.038c1.33921,-0.00325 2.43705,1.06133 2.475,2.4h7.948c0.03847,-1.33789 1.13556,-2.40172 2.474,-2.399c0.11276,0.00184 0.22516,0.01321 0.336,0.034zM10.343,5.231c0.31189,0.36828 0.55631,0.78873 0.722,1.242c0.36,0.987 0.36,2.068 0,3.055c-0.166,0.453 -0.41,0.873 -0.721,1.243c-0.2937,0.34506 -0.65341,0.62794 -1.058,0.832c-0.81776,0.40581 -1.77824,0.40581 -2.596,0c-0.406,-0.204 -0.766,-0.487 -1.06,-0.832c-0.31,-0.367 -0.549,-0.789 -0.706,-1.243c-0.34328,-0.9894 -0.34328,-2.0656 0,-3.055c0.15504,-0.45349 0.39234,-0.87453 0.7,-1.242c0.29429,-0.34528 0.6547,-0.62816 1.06,-0.832c0.40423,-0.2014 0.8504,-0.3042 1.302,-0.3c0.45185,-0.00653 0.89857,0.0964 1.302,0.3c0.40351,0.2044 0.76218,0.48725 1.055,0.832M9.334,9.351h-0.422c-0.08583,0.00545 -0.16953,-0.02816 -0.22776,-0.09144c-0.05824,-0.06328 -0.08478,-0.14948 -0.07224,-0.23456v-3.205h-0.883c-0.12661,0.35602 -0.4801,0.57943 -0.856,0.541h-0.17v0.57h0.735v2.093c0.01178,0.08493 -0.01503,0.17068 -0.07309,0.23377c-0.05806,0.06309 -0.14129,0.09692 -0.22691,0.09223h-0.448v0.57h2.642z";
@@ -648,7 +667,7 @@ function requireReact_development() {
648
667
  var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
649
668
  var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
650
669
  var REACT_MEMO_TYPE = Symbol.for("react.memo");
651
- var REACT_LAZY_TYPE = Symbol.for("react.lazy");
670
+ var REACT_LAZY_TYPE2 = Symbol.for("react.lazy");
652
671
  var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
653
672
  var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
654
673
  var FAUX_ITERATOR_SYMBOL = "@@iterator";
@@ -984,7 +1003,7 @@ function requireReact_development() {
984
1003
  return outerName;
985
1004
  }
986
1005
  return getComponentNameFromType(type.type) || "Memo";
987
- case REACT_LAZY_TYPE: {
1006
+ case REACT_LAZY_TYPE2: {
988
1007
  var lazyComponent = type;
989
1008
  var payload = lazyComponent._payload;
990
1009
  var init = lazyComponent._init;
@@ -1534,7 +1553,7 @@ function requireReact_development() {
1534
1553
  _result: ctor
1535
1554
  };
1536
1555
  var lazyType = {
1537
- $$typeof: REACT_LAZY_TYPE,
1556
+ $$typeof: REACT_LAZY_TYPE2,
1538
1557
  _payload: payload,
1539
1558
  _init: lazyInitializer
1540
1559
  };
@@ -1623,7 +1642,7 @@ function requireReact_development() {
1623
1642
  return true;
1624
1643
  }
1625
1644
  if (typeof type === "object" && type !== null) {
1626
- if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
1645
+ if (type.$$typeof === REACT_LAZY_TYPE2 || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
1627
1646
  // types supported by any Flight configuration anywhere since
1628
1647
  // we don't know which Flight build this will end up being used
1629
1648
  // with.
@@ -1978,7 +1997,7 @@ function requireReact_development() {
1978
1997
  return describeFunctionComponentFrame(type.render);
1979
1998
  case REACT_MEMO_TYPE:
1980
1999
  return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
1981
- case REACT_LAZY_TYPE: {
2000
+ case REACT_LAZY_TYPE2: {
1982
2001
  var lazyComponent = type;
1983
2002
  var payload = lazyComponent._payload;
1984
2003
  var init = lazyComponent._init;
@@ -2513,6 +2532,10 @@ function requireReact() {
2513
2532
  }
2514
2533
  var reactExports = requireReact();
2515
2534
  const React = /* @__PURE__ */ getDefaultExportFromCjs(reactExports);
2535
+ const React$1 = /* @__PURE__ */ _mergeNamespaces({
2536
+ __proto__: null,
2537
+ default: React
2538
+ }, [reactExports]);
2516
2539
  function LoadingSpinner() {
2517
2540
  return React.createElement("div", { className: "tds-loading-spinner" });
2518
2541
  }
@@ -2767,14 +2790,175 @@ const PageHeaderActionsDropdownButton = reactExports.forwardRef(({ className, ne
2767
2790
  return React.createElement(DropdownButton, { ref, ...props, kind: "secondary-page-header", ...needsAttentionProps({ className, needsAttention }) });
2768
2791
  });
2769
2792
  PageHeaderActionsDropdownButton.displayName = "PageHeaderActionsDropdownButton";
2770
- const BaseLink = reactExports.forwardRef(({ children, className, external = false, fullWidth, href, kind, label, prefix, size, suffix, ...restProps }, ref) => {
2793
+ function setRef(ref, value) {
2794
+ if (typeof ref === "function") {
2795
+ return ref(value);
2796
+ } else if (ref !== null && ref !== void 0) {
2797
+ ref.current = value;
2798
+ }
2799
+ }
2800
+ function composeRefs(...refs) {
2801
+ return (node) => {
2802
+ let hasCleanup = false;
2803
+ const cleanups = refs.map((ref) => {
2804
+ const cleanup = setRef(ref, node);
2805
+ if (!hasCleanup && typeof cleanup == "function") {
2806
+ hasCleanup = true;
2807
+ }
2808
+ return cleanup;
2809
+ });
2810
+ if (hasCleanup) {
2811
+ return () => {
2812
+ for (let i2 = 0; i2 < cleanups.length; i2++) {
2813
+ const cleanup = cleanups[i2];
2814
+ if (typeof cleanup == "function") {
2815
+ cleanup();
2816
+ } else {
2817
+ setRef(refs[i2], null);
2818
+ }
2819
+ }
2820
+ };
2821
+ }
2822
+ };
2823
+ }
2824
+ function useComposedRefs(...refs) {
2825
+ return reactExports.useCallback(composeRefs(...refs), refs);
2826
+ }
2827
+ // @__NO_SIDE_EFFECTS__
2828
+ function createSlot(ownerName) {
2829
+ const Slot2 = reactExports.forwardRef((props, forwardedRef) => {
2830
+ let { children, ...slotProps } = props;
2831
+ let slottableElement = null;
2832
+ let hasSlottable = false;
2833
+ const newChildren = [];
2834
+ if (isLazyComponent(children) && typeof use === "function") {
2835
+ children = use(children._payload);
2836
+ }
2837
+ reactExports.Children.forEach(children, (maybeSlottable) => {
2838
+ if (isSlottable(maybeSlottable)) {
2839
+ hasSlottable = true;
2840
+ const slottable = maybeSlottable;
2841
+ let child = "child" in slottable.props ? slottable.props.child : slottable.props.children;
2842
+ if (isLazyComponent(child) && typeof use === "function") {
2843
+ child = use(child._payload);
2844
+ }
2845
+ slottableElement = getSlottableElementFromSlottable(slottable, child);
2846
+ newChildren.push(slottableElement?.props?.children);
2847
+ } else {
2848
+ newChildren.push(maybeSlottable);
2849
+ }
2850
+ });
2851
+ if (slottableElement) {
2852
+ slottableElement = reactExports.cloneElement(slottableElement, void 0, newChildren);
2853
+ } else if (
2854
+ // A `Slottable` was found but it didn't resolve to a single element (e.g.
2855
+ // it wrapped multiple elements, text, or a render-prop `child` that
2856
+ // wasn't an element). Don't fall back to treating the `Slottable` wrapper
2857
+ // itself as the slot target — throw a descriptive error below instead.
2858
+ !hasSlottable && reactExports.Children.count(children) === 1 && reactExports.isValidElement(children)
2859
+ ) {
2860
+ slottableElement = children;
2861
+ }
2862
+ const slottableElementRef = slottableElement ? getElementRef(slottableElement) : void 0;
2863
+ const composedRef = useComposedRefs(forwardedRef, slottableElementRef);
2864
+ if (!slottableElement) {
2865
+ if (children || children === 0) {
2866
+ throw new Error(
2867
+ hasSlottable ? createSlottableError(ownerName) : createSlotError(ownerName)
2868
+ );
2869
+ }
2870
+ return children;
2871
+ }
2872
+ const mergedProps = mergeProps(slotProps, slottableElement.props ?? {});
2873
+ if (slottableElement.type !== reactExports.Fragment) {
2874
+ mergedProps.ref = forwardedRef ? composedRef : slottableElementRef;
2875
+ }
2876
+ return reactExports.cloneElement(slottableElement, mergedProps);
2877
+ });
2878
+ Slot2.displayName = `${ownerName}.Slot`;
2879
+ return Slot2;
2880
+ }
2881
+ var Slot = /* @__PURE__ */ createSlot("Slot");
2882
+ var SLOTTABLE_IDENTIFIER = Symbol.for("radix.slottable");
2883
+ // @__NO_SIDE_EFFECTS__
2884
+ function createSlottable(ownerName) {
2885
+ const Slottable2 = (props) => "child" in props ? props.children(props.child) : props.children;
2886
+ Slottable2.displayName = `${ownerName}.Slottable`;
2887
+ Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
2888
+ return Slottable2;
2889
+ }
2890
+ var Slottable = /* @__PURE__ */ createSlottable("Slottable");
2891
+ var getSlottableElementFromSlottable = (slottable, child) => {
2892
+ if ("child" in slottable.props) {
2893
+ const child2 = slottable.props.child;
2894
+ if (!reactExports.isValidElement(child2)) return null;
2895
+ return reactExports.cloneElement(child2, void 0, slottable.props.children(child2.props.children));
2896
+ }
2897
+ return reactExports.isValidElement(child) ? child : null;
2898
+ };
2899
+ function mergeProps(slotProps, childProps) {
2900
+ const overrideProps = { ...childProps };
2901
+ for (const propName in childProps) {
2902
+ const slotPropValue = slotProps[propName];
2903
+ const childPropValue = childProps[propName];
2904
+ const isHandler = /^on[A-Z]/.test(propName);
2905
+ if (isHandler) {
2906
+ if (slotPropValue && childPropValue) {
2907
+ overrideProps[propName] = (...args) => {
2908
+ const result = childPropValue(...args);
2909
+ slotPropValue(...args);
2910
+ return result;
2911
+ };
2912
+ } else if (slotPropValue) {
2913
+ overrideProps[propName] = slotPropValue;
2914
+ }
2915
+ } else if (propName === "style") {
2916
+ overrideProps[propName] = { ...slotPropValue, ...childPropValue };
2917
+ } else if (propName === "className") {
2918
+ overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
2919
+ }
2920
+ }
2921
+ return { ...slotProps, ...overrideProps };
2922
+ }
2923
+ function getElementRef(element) {
2924
+ let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
2925
+ let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
2926
+ if (mayWarn) {
2927
+ return element.ref;
2928
+ }
2929
+ getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
2930
+ mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
2931
+ if (mayWarn) {
2932
+ return element.props.ref;
2933
+ }
2934
+ return element.props.ref || element.ref;
2935
+ }
2936
+ function isSlottable(child) {
2937
+ return reactExports.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
2938
+ }
2939
+ var REACT_LAZY_TYPE = Symbol.for("react.lazy");
2940
+ function isLazyComponent(element) {
2941
+ return element != null && typeof element === "object" && "$$typeof" in element && element.$$typeof === REACT_LAZY_TYPE && "_payload" in element && isPromiseLike(element._payload);
2942
+ }
2943
+ function isPromiseLike(value) {
2944
+ return typeof value === "object" && value !== null && "then" in value;
2945
+ }
2946
+ var createSlotError = (ownerName) => {
2947
+ return `${ownerName} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`;
2948
+ };
2949
+ var createSlottableError = (ownerName) => {
2950
+ return `${ownerName} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`;
2951
+ };
2952
+ var use = React$1[" use ".trim().toString()];
2953
+ const BaseLink = reactExports.forwardRef(({ asChild = false, children, className, external = false, fullWidth, href, kind, label, prefix, size, suffix, ...restProps }, ref) => {
2771
2954
  const combinedClassName = classNames(kind && "tds-btn", size && size !== "md" && kind && COMPONENT_SIZE_CLASS_MAP[size], kind && COMPONENT_KIND_CLASS_MAP[kind], fullWidth && "tds-btn--full-width", className, { "tds-btn--prefix": prefix, "tds-btn--suffix": suffix });
2772
2955
  const externalProps = external ? { rel: "noopener noreferrer", target: "_blank" } : {};
2956
+ const Comp = asChild ? Slot : "a";
2773
2957
  return React.createElement(
2774
- "a",
2775
- { href, className: combinedClassName, ref, ...restProps, ...externalProps },
2958
+ Comp,
2959
+ { ...!asChild && { href }, className: combinedClassName, ref, ...restProps, ...externalProps },
2776
2960
  wrapStringWithSpan(prefix),
2777
- label || children,
2961
+ React.createElement(Slottable, null, label || children),
2778
2962
  wrapStringWithSpan(suffix)
2779
2963
  );
2780
2964
  });
@@ -2784,8 +2968,8 @@ const IconLink = reactExports.forwardRef(({ className, href, icon, ...restProps
2784
2968
  return React.createElement(BaseLink, { href, label: icon, className: iconOnlyClassName, ref, ...restProps });
2785
2969
  });
2786
2970
  IconLink.displayName = "IconLink";
2787
- const Link = reactExports.forwardRef(({ href, ...restProps }, ref) => {
2788
- return React.createElement(BaseLink, { href, ref, ...restProps });
2971
+ const Link = reactExports.forwardRef((props, ref) => {
2972
+ return React.createElement(BaseLink, { ref, ...props });
2789
2973
  });
2790
2974
  Link.displayName = "Link";
2791
2975
  const tokens = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/tapestry-migration-cli",
3
- "version": "3.6.0",
3
+ "version": "3.6.1-qa-1035.0",
4
4
  "description": "CLI tool for Tapestry migrations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "devDependencies": {
32
32
  "@emotion/react": "^11.14.0",
33
- "@planningcenter/tapestry": "^3.6.0",
33
+ "@planningcenter/tapestry": "^3.6.1-qa-1035.0",
34
34
  "@planningcenter/tapestry-react": "^4.11.5",
35
35
  "@types/jscodeshift": "^17.3.0",
36
36
  "@types/node": "^20.0.0",
@@ -50,5 +50,5 @@
50
50
  "publishConfig": {
51
51
  "access": "public"
52
52
  },
53
- "gitHead": "0009d96d1c39de5978d2cdea8334ca5120974137"
53
+ "gitHead": "2b87acce23b1df6872d7bbeadd648b117c9962c4"
54
54
  }
@@ -99,6 +99,157 @@ export default function Test() {
99
99
  expect(result).not.toContain("innerRef")
100
100
  })
101
101
 
102
+ it("applies paddingPropsToFlex through the chain", () => {
103
+ const input = `
104
+ import { StackView } from "@planningcenter/tapestry-react"
105
+
106
+ export default function Test() {
107
+ return <StackView axis="horizontal" padding="8px" paddingHorizontal="16px">Test</StackView>
108
+ }
109
+ `.trim()
110
+
111
+ const result = applyTransform(input)
112
+ expect(result).toContain("padding={1}")
113
+ expect(result).toContain("paddingInline={2}")
114
+ expect(result).not.toContain('padding="8px"')
115
+ expect(result).not.toContain("paddingHorizontal")
116
+ })
117
+
118
+ it("applies flexPropToExpand through the chain — flex={1} becomes expand", () => {
119
+ const input = `
120
+ import { StackView } from "@planningcenter/tapestry-react"
121
+
122
+ export default function Test() {
123
+ return <StackView flex={1}>Test</StackView>
124
+ }
125
+ `.trim()
126
+
127
+ const result = applyTransform(input)
128
+ expect(result).toContain("expand")
129
+ expect(result).not.toContain("flex")
130
+ })
131
+
132
+ it('applies flexPropToExpand through the chain — flex="1 0 0" becomes grow/shrink/basis', () => {
133
+ const input = `
134
+ import { StackView } from "@planningcenter/tapestry-react"
135
+
136
+ export default function Test() {
137
+ return <StackView flex="1 0 0">Test</StackView>
138
+ }
139
+ `.trim()
140
+
141
+ const result = applyTransform(input)
142
+ expect(result).toContain("grow={1}")
143
+ expect(result).toContain("shrink={0}")
144
+ expect(result).toContain('basis="0"')
145
+ expect(result).not.toContain("flex=")
146
+ })
147
+
148
+ it("applies mediaQueriesToResponsive through the chain", () => {
149
+ const input = `
150
+ import { StackView } from "@planningcenter/tapestry-react"
151
+
152
+ export default function Test() {
153
+ return <StackView axis="horizontal" mediaQueries={{ md: { axis: "vertical" } }}>Test</StackView>
154
+ }
155
+ `.trim()
156
+
157
+ const result = applyTransform(input)
158
+ expect(result).toContain("responsive=")
159
+ expect(result).toContain('direction: "column"')
160
+ expect(result).not.toContain("mediaQueries=")
161
+ })
162
+
163
+ it("applies cssFlexAliasesToProps through the chain — flexDirection does not produce a double direction prop", () => {
164
+ const input = `
165
+ import { StackView } from "@planningcenter/tapestry-react"
166
+
167
+ export default function Test() {
168
+ return <StackView flexDirection="row">Test</StackView>
169
+ }
170
+ `.trim()
171
+
172
+ const result = applyTransform(input)
173
+ expect(result).toContain('direction="row"')
174
+ expect(result?.match(/direction=/g)).toHaveLength(1)
175
+ })
176
+
177
+ it("applies cssFlexAliasesToProps through the chain — unmappable justifyContent stays as JSX prop with TODO, not in style={{}}", () => {
178
+ const input = `
179
+ import { StackView } from "@planningcenter/tapestry-react"
180
+
181
+ export default function Test() {
182
+ return <StackView justifyContent="stretch">Test</StackView>
183
+ }
184
+ `.trim()
185
+
186
+ const result = applyTransform(input)
187
+ expect(result).toContain("TODO")
188
+ expect(result).toContain("justifyContent")
189
+ expect(result).not.toContain("style={{")
190
+ })
191
+
192
+ it("applies cssFlexAliasesToProps through the chain — flexGrow/flexShrink/flexBasis become Flex props", () => {
193
+ const input = `
194
+ import { StackView } from "@planningcenter/tapestry-react"
195
+
196
+ export default function Test() {
197
+ return <StackView flexGrow={1} flexShrink={0} flexBasis="auto">Test</StackView>
198
+ }
199
+ `.trim()
200
+
201
+ const result = applyTransform(input)
202
+ expect(result).toContain("grow={1}")
203
+ expect(result).toContain("shrink={0}")
204
+ expect(result).toContain('basis="auto"')
205
+ expect(result).not.toContain("flexGrow")
206
+ expect(result).not.toContain("flexShrink")
207
+ expect(result).not.toContain("flexBasis")
208
+ expect(result).not.toContain("style={{")
209
+ })
210
+
211
+ it("applies convertStyleProps through the chain", () => {
212
+ const input = `
213
+ import { StackView } from "@planningcenter/tapestry-react"
214
+
215
+ export default function Test() {
216
+ return <StackView axis="horizontal" marginTop={2}>Test</StackView>
217
+ }
218
+ `.trim()
219
+
220
+ const result = applyTransform(input)
221
+ expect(result).toContain("style={{")
222
+ expect(result).toContain("marginTop:")
223
+ expect(result).not.toContain("marginTop={2}")
224
+ })
225
+
226
+ it("applies unsupportedProps through the chain", () => {
227
+ const input = `
228
+ import { StackView } from "@planningcenter/tapestry-react"
229
+
230
+ export default function Test() {
231
+ return <StackView axis="horizontal" visible={isOpen}>Test</StackView>
232
+ }
233
+ `.trim()
234
+
235
+ const result = applyTransform(input)
236
+ expect(result).toContain("TODO: tapestry-migration (visible)")
237
+ })
238
+
239
+ it("applies auditSpreadProps through the chain", () => {
240
+ const input = `
241
+ import { StackView } from "@planningcenter/tapestry-react"
242
+
243
+ export default function Test() {
244
+ return <StackView axis="horizontal" {...props}>Test</StackView>
245
+ }
246
+ `.trim()
247
+
248
+ const result = applyTransform(input)
249
+ expect(result).toContain("TODO: tapestry-migration (spreadAttribute)")
250
+ expect(result).toContain("{...props}")
251
+ })
252
+
102
253
  it("applies moveFlexImport through the chain", () => {
103
254
  const input = `
104
255
  import { StackView } from "@planningcenter/tapestry-react"
@@ -1,24 +1,38 @@
1
1
  import { Transform } from "jscodeshift"
2
2
 
3
3
  import alignmentToAlign from "./transforms/alignmentToAlign"
4
+ import auditSpreadProps from "./transforms/auditSpreadProps"
4
5
  import axisToDirection from "./transforms/axisToDirection"
6
+ import convertStyleProps from "./transforms/convertStyleProps"
7
+ import cssFlexAliasesToProps from "./transforms/cssFlexAliasesToProps"
5
8
  import distributionToJustify from "./transforms/distributionToJustify"
9
+ import flexPropToExpand from "./transforms/flexPropToExpand"
6
10
  import innerRefToRef from "./transforms/innerRefToRef"
11
+ import mediaQueriesToResponsive from "./transforms/mediaQueriesToResponsive"
7
12
  import moveFlexImport from "./transforms/moveFlexImport"
13
+ import paddingPropsToFlex from "./transforms/paddingPropsToFlex"
8
14
  import setDefaultAxis from "./transforms/setDefaultAxis"
9
15
  import spacingToGap from "./transforms/spacingToGap"
16
+ import unsupportedProps from "./transforms/unsupportedProps"
10
17
 
11
18
  const transform: Transform = (fileInfo, api, options) => {
12
19
  let currentSource = fileInfo.source
13
20
  let hasAnyChanges = false
14
21
 
15
22
  const transforms = [
23
+ cssFlexAliasesToProps,
16
24
  setDefaultAxis,
25
+ paddingPropsToFlex,
26
+ flexPropToExpand,
17
27
  axisToDirection,
18
28
  alignmentToAlign,
19
29
  distributionToJustify,
20
30
  spacingToGap,
21
31
  innerRefToRef,
32
+ mediaQueriesToResponsive,
33
+ convertStyleProps,
34
+ unsupportedProps,
35
+ auditSpreadProps,
22
36
  moveFlexImport,
23
37
  ]
24
38
 
@@ -0,0 +1,93 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./auditSpreadProps"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ const AUDIT_COMMENT =
9
+ "TODO: tapestry-migration (spreadAttribute): Spread props can contain unsupported props, please explore usages and migrate as needed."
10
+
11
+ function applyTransform(source: string): string | null {
12
+ const fileInfo = { path: "test.tsx", source }
13
+ return transform(
14
+ fileInfo,
15
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
16
+ {}
17
+ ) as string | null
18
+ }
19
+
20
+ describe("auditSpreadProps transform", () => {
21
+ it("adds a comment to StackView with spread props", () => {
22
+ const input = `
23
+ import { StackView } from "@planningcenter/tapestry-react"
24
+
25
+ export default function Test() {
26
+ return <StackView {...props}>Test</StackView>
27
+ }
28
+ `.trim()
29
+
30
+ const result = applyTransform(input)
31
+ expect(result).toContain(AUDIT_COMMENT)
32
+ expect(result).toContain("{...props}")
33
+ })
34
+
35
+ it("adds a comment for each spread", () => {
36
+ const input = `
37
+ import { StackView } from "@planningcenter/tapestry-react"
38
+
39
+ export default function Test() {
40
+ return <StackView {...baseProps} {...extraProps}>Test</StackView>
41
+ }
42
+ `.trim()
43
+
44
+ const result = applyTransform(input)
45
+ const commentCount = (result?.match(/spreadAttribute/g) || []).length
46
+ expect(commentCount).toBe(2)
47
+ })
48
+
49
+ it("preserves all other attributes", () => {
50
+ const input = `
51
+ import { StackView } from "@planningcenter/tapestry-react"
52
+
53
+ export default function Test() {
54
+ return <StackView axis="horizontal" spacing={2} {...props}>Test</StackView>
55
+ }
56
+ `.trim()
57
+
58
+ const result = applyTransform(input)
59
+ expect(result).toContain('axis="horizontal"')
60
+ expect(result).toContain("spacing={2}")
61
+ expect(result).toContain("{...props}")
62
+ })
63
+
64
+ describe("no changes scenarios", () => {
65
+ it("returns null when no spread props", () => {
66
+ const input = `
67
+ import { StackView } from "@planningcenter/tapestry-react"
68
+
69
+ export default function Test() {
70
+ return <StackView axis="horizontal">Test</StackView>
71
+ }
72
+ `.trim()
73
+
74
+ expect(applyTransform(input)).toBe(null)
75
+ })
76
+
77
+ it("returns null when not imported from tapestry-react", () => {
78
+ const input = `
79
+ import { StackView } from "some-other-library"
80
+
81
+ export default function Test() {
82
+ return <StackView {...props}>Test</StackView>
83
+ }
84
+ `.trim()
85
+
86
+ expect(applyTransform(input)).toBe(null)
87
+ })
88
+
89
+ it("returns null for empty file", () => {
90
+ expect(applyTransform("")).toBe(null)
91
+ })
92
+ })
93
+ })
@@ -0,0 +1,6 @@
1
+ import { commentOnSpreadPropsFactory } from "../../shared/transformFactories/commentOnSpreadPropsFactory"
2
+
3
+ export default commentOnSpreadPropsFactory({
4
+ targetComponent: "StackView",
5
+ targetPackage: "@planningcenter/tapestry-react",
6
+ })
@@ -101,6 +101,21 @@ export default function Test({ orientation }) {
101
101
  expect(result).not.toContain('direction="')
102
102
  })
103
103
 
104
+ it("migrates a ternary axis value to a ternary direction value", () => {
105
+ const input = `
106
+ import { StackView } from "@planningcenter/tapestry-react"
107
+
108
+ export default function Test() {
109
+ return <StackView axis={isNarrow ? "vertical" : "horizontal"}>Test</StackView>
110
+ }
111
+ `.trim()
112
+
113
+ const result = applyTransform(input)
114
+ expect(result).toContain('direction={isNarrow ? "column" : "row"}')
115
+ expect(result).not.toContain("axis")
116
+ expect(result).not.toContain("TODO")
117
+ })
118
+
104
119
  it("maps an aliased StackView import", () => {
105
120
  const input = `
106
121
  import { StackView as Stack } from "@planningcenter/tapestry-react"
@@ -31,6 +31,32 @@ const transform: Transform = attributeTransformFactory({
31
31
  const direction = resolved ? AXIS_DIRECTION_MAP[resolved] : undefined
32
32
 
33
33
  if (!direction) {
34
+ const value = axisAttribute.value
35
+ if (value?.type === "JSXExpressionContainer") {
36
+ const expr = value.expression
37
+ if (
38
+ expr.type === "ConditionalExpression" &&
39
+ expr.consequent.type === "StringLiteral" &&
40
+ expr.alternate.type === "StringLiteral"
41
+ ) {
42
+ const consequentDir =
43
+ AXIS_DIRECTION_MAP[expr.consequent.value as string]
44
+ const alternateDir =
45
+ AXIS_DIRECTION_MAP[expr.alternate.value as string]
46
+ if (consequentDir && alternateDir) {
47
+ axisAttribute.name.name = "direction"
48
+ axisAttribute.value = j.jsxExpressionContainer(
49
+ j.conditionalExpression(
50
+ expr.test,
51
+ j.stringLiteral(consequentDir),
52
+ j.stringLiteral(alternateDir)
53
+ )
54
+ )
55
+ return true
56
+ }
57
+ }
58
+ }
59
+
34
60
  addComment({
35
61
  element,
36
62
  j,