Repository: infernojs/inferno
Pull request #1613 (2022-10-22 18:28:08)

Implement className as function for compatibility with react-router.
Based on https://github.com/remix-run/react-router/commit/b3c7d9909e461256debf7f0ea3cd199912af0b64

This PR closes Issue #1607

UPDATE: Test pass now after having done a rebase on master. (I didn't run the tests before rebase so not sure if the problem was solved that way or just resolved itself)

coveralls: 
[![Coverage Status](https://coveralls.io/builds/53511789/badge)](https://coveralls.io/builds/53511789)

Coverage increased (+0.004%) to 93.273% when pulling **ab86a0a0fe4b3c5a28d199fb65d0478944eb9ed4 on jhsware:fix-inferno-router-navlink** into **8e89b766f28d6bfb2179e0f0be1be4bed677c207 on infernojs:master**.

Havunen: LGTM, thx!

Patch:
diff --git a/packages/inferno-router/src/NavLink.ts b/packages/inferno-router/src/NavLink.ts
index 943d84552..2ba409609 100644
--- a/packages/inferno-router/src/NavLink.ts
+++ b/packages/inferno-router/src/NavLink.ts
@@ -9,14 +9,15 @@ function filter(i) {
   return i;
 }
 
-interface NavLinkProps extends ILinkProps {
+interface NavLinkProps extends Omit<ILinkProps, 'className'> {
   to: string | Location;
   exact?: boolean;
   strict?: boolean;
   location?: any;
   activeClassName?: string;
+  className?: string | ((isActive: boolean) => string);
   activeStyle?: any;
-  style?: any;
+  style?: object | ((isActive: boolean) => string);
   isActive?: (match, location) => boolean;
   ariaCurrent?: string;
 }
@@ -32,22 +33,30 @@ export function NavLink({
   onClick,
   location: linkLocation,
   activeClassName = 'active',
-  className,
+  className: classNameProp,
   activeStyle,
-  style,
+  style: styleProp,
   isActive: getIsActive,
   ariaCurrent = 'true',
   ...rest
-}: NavLinkProps & Inferno.LinkHTMLAttributes<HTMLLinkElement>): any {
+}: NavLinkProps & Omit<Inferno.LinkHTMLAttributes<HTMLLinkElement>, 'className' | 'style'>): any {
   function linkComponent({ location, match }): VNode {
     const isActive = Boolean(getIsActive ? getIsActive(match, location) : match);
 
+    const className = typeof classNameProp === "function"
+      ? classNameProp(isActive)
+      : classNameProp;
+
+    const style = typeof styleProp === "function"
+      ? styleProp(isActive)
+      : styleProp;
+
     return createComponentVNode(
       VNodeFlags.ComponentFunction,
       Link,
       combineFrom(
         {
-          'aria-current': isActive && ariaCurrent,
+          'aria-current': (isActive && ariaCurrent) || null,
           className: isActive ? [className, activeClassName].filter(filter).join(' ') : className,
           onClick,
           style: isActive ? combineFrom(style, activeStyle) : style,


Test patch:
diff --git a/packages/inferno-router/__tests__/NavLink.spec.tsx b/packages/inferno-router/__tests__/NavLink.spec.tsx
index 1bc968d0b..c2330a828 100644
--- a/packages/inferno-router/__tests__/NavLink.spec.tsx
+++ b/packages/inferno-router/__tests__/NavLink.spec.tsx
@@ -158,6 +158,80 @@ describe('NavLink', () => {
     });
   });
 
+  it("applies its className when provided as a function", () => {
+    render(
+      <MemoryRouter initialEntries={["/pizza"]}>
+        <NavLink
+          to="/pizza"
+          className={(isActive: boolean) => isActive ? "active-pizza" : "chill-pizza"}
+        >
+        Pizza!
+      </NavLink>
+      </MemoryRouter >,
+      node
+    );
+
+    const a = node.querySelector("a");
+    expect(a.className).toContain("active-pizza");
+  });
+
+  it("applies its style when provided as a function", () => {
+    const defaultStyle = { color: "black" };
+    const activeStyle = { color: "red" };
+
+    render(
+      <MemoryRouter initialEntries={["/pizza"]}>
+        <NavLink
+          to="/pizza"
+          style={isActive => (isActive ? activeStyle : defaultStyle)}
+        >
+          Pizza!
+        </NavLink>
+      </MemoryRouter>,
+      node
+    );
+
+    const a = node.querySelector("a");
+    expect(a.style.color).toBe(activeStyle.color);
+  });
+
+  it("applies its className when provided as a function", () => {
+    render(
+      <MemoryRouter initialEntries={["/pizza"]}>
+        <NavLink
+          to="/salad"
+          className={isActive => (isActive ? "active-salad" : "chill-salad")}
+        >
+          Salad?
+        </NavLink>
+      </MemoryRouter>,
+      node
+    );
+
+    const a = node.querySelector("a");
+    expect(a.className).toContain("chill-salad");
+  });
+
+  it("applies its style when provided as a function", () => {
+    const defaultStyle = { color: "black" };
+    const activeStyle = { color: "red" };
+
+    render(
+      <MemoryRouter initialEntries={["/pizza"]}>
+        <NavLink
+          to="/salad"
+          style={isActive => (isActive ? activeStyle : defaultStyle)}
+        >
+          Salad?
+        </NavLink>
+      </MemoryRouter>,
+      node
+    );
+
+    const a = node.querySelector("a");
+    expect(a.style.color).toBe(defaultStyle.color);
+  });
+
   describe('exact', () => {
     it('does not do exact matching by default', () => {
       render(