Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tidy-tools-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-bottom-tabs": minor
---

Add the `$RNBottomTabsEnableSVG` Podfile flag, the `enableSVG` Expo plugin option, and the `RN_BOTTOM_TABS_ENABLE_SVG` environment variable for Swift Package Manager. Set the flag to `false` to remove the private CoreSVG code from Apple builds. When the flag is not set, SVG decoding stays enabled and `pod install` shows a deprecation warning. The next major version disables SVG decoding by default.
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- name: Typecheck files
run: yarn typecheck

- name: Test SVG configuration
run: yarn workspace react-native-bottom-tabs test:ruby

swift-lint:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -157,8 +160,19 @@ jobs:
yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"

build-ios-swiftpm:
name: ${{ matrix.svg.name == 'default' && 'build-ios-swiftpm' || 'build-ios-swiftpm-svg-disabled' }}
runs-on: macos-26
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
svg:
- name: default
value: ''
- name: disabled
value: '0'
env:
RN_BOTTOM_TABS_ENABLE_SVG: ${{ matrix.svg.value }}
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
Expand All @@ -181,3 +195,57 @@ jobs:
ARCHS=arm64 \
CODE_SIGNING_ALLOWED=NO \
build

- name: Check that no private CoreSVG code is linked
if: matrix.svg.name == 'disabled'
run: |
app="$RUNNER_TEMP/example-swiftpm-build/Build/Products/Release-iphonesimulator/ExampleSwiftPM.app"
if find "$app" -type f -perm +111 -exec strings -a {} + | grep -E 'CoreSVGWrapper|Q0dTVkdEb2N1bWVud'; then
echo "::error::The SVG-disabled build still contains private CoreSVG code."
exit 1
fi

build-ios-svg-disabled:
runs-on: macos-26
timeout-minutes: 45
env:
RN_BOTTOM_TABS_ENABLE_SVG: '0'
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

- name: Setup
uses: ./.github/actions/setup

- name: Build package
run: yarn build

- name: Install cocoapods and plugins
run: |
gem install cocoapods-swift-modular-headers
cd apps/example
pod install --project-directory=ios
env:
RCT_NEW_ARCH_ENABLED: 1
NO_FLIPPER: 1

- name: Build example for iOS with SVG disabled
run: |
xcodebuild \
-workspace apps/example/ios/ReactNativeBottomTabsExample.xcworkspace \
-scheme ReactTestApp \
-configuration Debug \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$RUNNER_TEMP/example-svg-disabled-build" \
ARCHS=arm64 \
CODE_SIGNING_ALLOWED=NO \
build

- name: Check that no private CoreSVG code is linked
run: |
app="$RUNNER_TEMP/example-svg-disabled-build/Build/Products/Debug-iphonesimulator/ReactTestApp.app"
if find "$app" -type f -perm +111 -exec strings -a {} + | grep -E 'CoreSVGWrapper|Q0dTVkdEb2N1bWVud'; then
echo "::error::The SVG-disabled build still contains private CoreSVG code."
exit 1
fi
50 changes: 50 additions & 0 deletions docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,56 @@ Function that given `{ focused: boolean }` returns `ImageSource` or `AppleIcon`
SF Symbols are only supported on Apple platforms.
:::

#### SVG images on Apple platforms

On Apple platforms, this library adds an SVG decoder to React Native's image loader. The decoder handles SVG tab icons. It also handles SVG sources in every React Native `<Image>` in your app. It uses private CoreSVG APIs. Android does not use this decoder.

:::warning
The next major version disables the SVG decoder by default. Set the flag below now to choose the behavior that you want.
:::

To control the decoder, set `$RNBottomTabsEnableSVG` in your `Podfile`, before React Native configures pods. Then run `pod install` and rebuild the native app:

```ruby
# Keep SVG tab icons and SVG images:
$RNBottomTabsEnableSVG = true

# Or remove the private CoreSVG code from your app:
$RNBottomTabsEnableSVG = false
```

| Value | This version | Next major version |
| --- | --- | --- |
| not set | Enabled, with a warning during `pod install` | Disabled |
| `true` | Enabled | Enabled |
| `false` | Disabled. No private CoreSVG code is compiled. | Disabled |

When the decoder is disabled, SVG sources do not show, and development builds log a warning to the JavaScript console. React Native DevTools shows the full message, and LogBox shows that a warning exists. PNG, JPEG, and SF Symbol icons do not need the decoder.

You can also set the `RN_BOTTOM_TABS_ENABLE_SVG` environment variable to `1` or `0`. If the `Podfile` also sets `$RNBottomTabsEnableSVG`, the `Podfile` value is used.

If you use Expo prebuild, set the `enableSVG` option of the config plugin to `true` or `false`:

```json
{
"expo": {
"plugins": [["react-native-bottom-tabs", { "enableSVG": false }]]
}
}
```

If your app uses Swift Package Manager instead of CocoaPods, set `RN_BOTTOM_TABS_ENABLE_SVG` to `1` or `0` in the environment when SwiftPM resolves packages. For example, run the sync and the build from the same shell:

```sh
export RN_BOTTOM_TABS_ENABLE_SVG=0
npx react-native spm
npx react-native run-ios
```

Xcode reads this variable only when it is set in Xcode's own environment. If you open the project from the Dock or Finder, the variable from your shell does not reach it.

When the flag is not set, development builds also log the deprecation warning to the JavaScript console the first time that the app decodes an SVG image. With Swift Package Manager, this is the only place where the warning shows, because there is no `pod install` step.

#### `tabBarIconRenderingMode`

Rendering mode for image icons.
Expand Down
32 changes: 31 additions & 1 deletion packages/react-native-bottom-tabs/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@

import PackageDescription

// The SVG image decoder uses private CoreSVG APIs. RN_BOTTOM_TABS_ENABLE_SVG controls it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Nit · Package.swift comment documents a different set of accepted values than the code it sits above

The new comment lists unset / 1 / true / 0 / false, but enableSVG also treats "no" as disabled, matching FALSE_VALUES in svg_configuration.rb whose own comment table lists its values. This is a third hand-copied list of accepted values that already disagrees with the code, and the next value added to TRUE_VALUES/FALSE_VALUES has to be remembered in all three places. Document "no" (and "yes" on the true side) or drop it, so the SwiftPM table matches the Ruby one.

Suggested change
// The SVG image decoder uses private CoreSVG APIs. RN_BOTTOM_TABS_ENABLE_SVG controls it
// The SVG image decoder uses private CoreSVG APIs. RN_BOTTOM_TABS_ENABLE_SVG controls it
// when SwiftPM resolves the package:
// unset -> enabled, with a deprecation notice in dev builds
// 1 / true / yes -> enabled
// 0 / false / no -> disabled, no private CoreSVG code is compiled
Details

persona: maintainability

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@apex-sentinel-staging like

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

// when SwiftPM resolves the package:
// unset -> enabled, with a deprecation notice in dev builds
// 1 / true / yes -> enabled
// 0 / false / no -> disabled, no private CoreSVG code is compiled
let svgRawSetting = Context.environment["RN_BOTTOM_TABS_ENABLE_SVG"] ?? ""
let svgSetting = String(
svgRawSetting.drop(while: \.isWhitespace).reversed().drop(while: \.isWhitespace).reversed()
).lowercased()
let svgSettingIsUnset = svgSetting.isEmpty
// TODO(next major): disable SVG when RN_BOTTOM_TABS_ENABLE_SVG is unset.
let enableSVG: Bool
switch svgSetting {
case "", "1", "true", "yes":
enableSVG = true
case "0", "false", "no":
enableSVG = false
default:
fatalError(
"[react-native-bottom-tabs] Unsupported value \"\(svgRawSetting)\" for RN_BOTTOM_TABS_ENABLE_SVG. "
+ "Use true or false (or 1 or 0)."
)
}
// CSetting and CXXSetting are different types, so both lists map these names.
let svgDefines = [
("RN_BOTTOM_TABS_ENABLE_SVG", enableSVG),
("RN_BOTTOM_TABS_SVG_DEFAULTED", enableSVG && svgSettingIsUnset),
].filter(\.1).map(\.0)

let reactHeaders: [Target.Dependency] = [
.product(name: "ReactHeaders", package: "ReactNative"),
.product(name: "ReactNativeHeaders", package: "ReactNative"),
Expand Down Expand Up @@ -64,7 +93,8 @@ let package = Package(
"common/cpp",
],
publicHeadersPath: "ios/include",
cxxSettings: [
cSettings: svgDefines.map { .define($0, to: "1") },
cxxSettings: svgDefines.map { .define($0, to: "1") } + [
.headerSearchPath("common/cpp"),
.define("RCT_NEW_ARCH_ENABLED", to: "1"),
// Fabric's C++ layout must match the selected React framework.
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native-bottom-tabs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

The full documentation can be found on our [website](https://oss.callstack.com/react-native-bottom-tabs/).

On Apple platforms, the SVG image decoder will be disabled by default in the next major version. See [SVG images on Apple platforms](https://oss.callstack.com/react-native-bottom-tabs/docs/guides/usage-with-react-navigation#svg-images-on-apple-platforms) to keep it or to remove it now.

## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native-bottom-tabs/ios/SVG/CoreSVG.mm
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// The private CoreSVG code is compiled only when SVG support is enabled.
// See ios/svg_configuration.rb and Package.swift.
#if RN_BOTTOM_TABS_ENABLE_SVG

#import "CoreSVG.h"
#import <dlfcn.h>
#import <objc/runtime.h>
Expand Down Expand Up @@ -175,3 +179,5 @@ + (BOOL)supportsVectorSVGImage {
}

@end

#endif
60 changes: 60 additions & 0 deletions packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm
Original file line number Diff line number Diff line change
@@ -1,13 +1,60 @@
#import "SvgDecoder.h"
#import <React/RCTLog.h>

#if RN_BOTTOM_TABS_ENABLE_SVG
#import "CoreSVG.h"
#endif

#if !RN_BOTTOM_TABS_ENABLE_SVG && RCT_DEV
static BOOL RNBottomTabsLooksLikeSVGData(NSData *data)
{
if (!data) {
return NO;
}
NSData *tag = [@"</svg>" dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger length = MIN(100, data.length);
return [data rangeOfData:tag options:NSDataSearchBackwards range:NSMakeRange(data.length - length, length)].location != NSNotFound;
}
#endif

@implementation SvgDecoder

RCT_EXPORT_MODULE()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Nit · @synthesize callableJSModules and RCTCallableJSModules rely on headers SvgDecoder.mm never imports

SvgDecoder.mm adds @synthesize callableJSModules = _callableJSModules; (line 24, compiled unconditionally, not inside #if RCT_DEV) and names the RCTCallableJSModules type (line 31), but the file imports only SvgDecoder.h and <React/RCTLog.h>, and neither of the two protocols SvgDecoder declares (SvgDecoder.h:6, RCTImageDataDecoder, NativeSVGDecoderSpec) is the place that property is declared in React Native — it comes from the image-loader side. So the declaration of both the property and the class must arrive transitively through whatever RCTLog.h/RCTImageDataDecoder.h happen to pull in today; @synthesize for a property that is not declared is a hard compile error, and an RN header reshuffle (RN has moved RCTCallableJSModules and the image-loader protocols more than once) breaks both Debug and Release builds of every consumer. I could not check the RN headers in this offline checkout, hence the lowered confidence; the same applies to RCT_DEV, which is only correct if RCTDefines.h really is imported by RCTLog.h on the supported RN versions. The disabled/RCT_DEV code paths are also the ones no CI job compiles as Release-with-dev-warnings, so a regression here surfaces only for a subset of consumers.

Suggested change
RCT_EXPORT_MODULE()
#import "SvgDecoder.h"
#import <React/RCTLog.h>
#import <React/RCTCallableJSModules.h>
#import <React/RCTImageLoaderProtocol.h>

Suggested change

Import the headers you use directly (#import <React/RCTCallableJSModules.h>, and the image-loader header that declares the callableJSModules property) rather than relying on transitive imports, and move @synthesize inside the same #if RCT_DEV guard as its only use so a release build does not depend on the property existing.

Details

persona: swift-ios

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@apex-sentinel-staging pivot RCTImageDataDecoder.h imports RCTBridge.h, and the protocol is declared as RCTImageDataDecoder . RCTBridgeModule.h declares the callableJSModules property in all builds and tells modules to add @synthesize callableJSModules. SvgDecoder.h imports RCTImageDataDecoder.h, so the declaration is a direct part of the protocol that SvgDecoder adopts

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Nit · @synthesize callableJSModules sits outside the #if RCT_DEV guard that covers its only reader

@synthesize callableJSModules = _callableJSModules; at SvgDecoder.mm:24 is emitted unconditionally, while the whole RCTImageDataDecoder protocol it satisfies and the logDevWarning: reader at line 31 are gated behind #if RCT_DEV (the RCTImageDataDecoder protocol declares @property (nonatomic, weak) RCTCallableJSModules *callableJSModules;). If that declaration turns out to be dev-only in a given React version (as the surrounding #if RCT_DEV blocks around the call sites assume), the synthesized accessor for an undeclared property is a hard error in the SVG-disabled Release build — exactly the configuration this PR adds a CI job for, and the reason @synthesize normally sits inside the same #if RCT_DEV guard as its only user. Either way it is inconsistent: the only consumer is dev-only, so the ivar/accessor are dead weight (and an unused-ivar/property warning surface) in every non-dev build.

Suggested change
RCT_EXPORT_MODULE()
#if RCT_DEV
@synthesize callableJSModules = _callableJSModules;
// RCTLogWarn alone does not show the message in React Native DevTools, so the

Suggested change

Move @synthesize callableJSModules = _callableJSModules; inside the existing #if RCT_DEV block that contains logDevWarning: (verifying whether RCTImageDataDecoder declares the property unconditionally), so the synthesized accessor is compiled exactly when something reads it.

Details

persona: javascript

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@apex-sentinel-staging pivot The property comes from RCTBridgeModule and is declared in all builds, not only in dev builds. Because SvgDecoder adopts the protocol, it must synthesize the property in Release too. If it does not, the compiler warns about a missing property implementation.


@synthesize callableJSModules = _callableJSModules;

#if RCT_DEV

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor · The dev warning is sent by invoking the JS module named "RCTLog" with method "logToConsole", but RCTLog is not a call…

The dev warning is sent by invoking the JS module named "RCTLog" with method "logToConsole", but RCTLog is not a callable JS module in React Native, so RCTCallableJSModules.invokeModule would drop the call and neither the DevTools warning nor the LogBox entry the PR description reports would appear; only NSLog would.

Details

Noted in the reviewer's candidate ledger and not refuted before the budget ran out.

persona: maintainability

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@apex-sentinel-staging pivot RCTLog is a callable JS module. Libraries/Core/setUpBatchedBridge.js registers it with registerModule('RCTLog', ...). I tested this with Metro: the warning shows in the React Native DevTools console.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Nit · logDevWarning silently drops the deprecation warning when callableJSModules is nil in a Release build

logDevWarning: is compiled only under #if RCT_DEV (line 26), but it reads self.callableJSModules, which RCTImageLoader only injects during its own (dev-only) initialization. In the CI job added by this PR the SwiftPM example is built with -configuration Release, where RCTImageLoader's #if RCT_DEV setup does not run, so callableJSModules stays nil and the else branch emits a plain RCTLogWarn — which is itself compiled out when RCT_DEV is 0. The result: the deprecation/disabled warning the PR describes as "logged to the JavaScript console" never reaches any consumer of a Release-with-dev-logging build; the same asymmetry applies to any app whose image decoding happens before the loader wires the decoder. At minimum the fallback path is dead code under the very configuration it is written for.

Details

persona: javascript

Reply @apex-sentinel-staging note ... to add context, or @apex-sentinel-staging pivot ... if this is wrong — I will re-check the cited code.

Useful? Reply @apex-sentinel-staging like, or @apex-sentinel-staging dislike <why> if it is not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@apex-sentinel-staging pivot logDevWarning: and all of its callers are compiled only when RCT_DEV is set. In a Release build there is no dev warning to lose, and the pod install warning covers that case. In dev builds, React Native sets callableJSModules on the module, and the DevTools test showed the warning.

// RCTLogWarn alone does not show the message in React Native DevTools, so the
// warning goes through the JS console, which also shows it in LogBox.
- (void)logDevWarning:(NSString *)message
{
RCTCallableJSModules *jsModules = self.callableJSModules;
if (jsModules) {
NSLog(@"%@", message);
[jsModules invokeModule:@"RCTLog" method:@"logToConsole" withArgs:@[ @"warn", message ]];
} else {
RCTLogWarn(@"%@", message);
}
}
#endif

- (BOOL)canDecodeImageData:(NSData *)imageData
{
#if RN_BOTTOM_TABS_ENABLE_SVG
return [CoreSVGWrapper isSVGData:imageData];
#else
#if RCT_DEV
if (RNBottomTabsLooksLikeSVGData(imageData)) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self logDevWarning:@"[react-native-bottom-tabs] SVG decoding is disabled, so SVG images are not shown. "
@"To enable it, set `$RNBottomTabsEnableSVG = true` in your Podfile, "
@"or `RN_BOTTOM_TABS_ENABLE_SVG=1` for Swift Package Manager."];
});
}
#endif
return NO;
#endif
}

- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
Expand All @@ -16,7 +63,20 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
resizeMode:(RCTResizeMode)resizeMode
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
#if RN_BOTTOM_TABS_ENABLE_SVG
#if RCT_DEV && RN_BOTTOM_TABS_SVG_DEFAULTED
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self logDevWarning:@"[react-native-bottom-tabs] SVG decoding will be disabled by default in the next major version. "
@"To keep it, set `$RNBottomTabsEnableSVG = true` in your Podfile, "
@"or `RN_BOTTOM_TABS_ENABLE_SVG=1` for Swift Package Manager. "
@"Set it to `false` (or `0`) to remove the private CoreSVG code now."];
});
#endif
UIImage *image = [CoreSVGWrapper.shared imageFromSVGData:imageData];
#else
UIImage *image = nil;
#endif

if (image) {
completionHandler(nil, image);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require "minitest/autorun"
require_relative "../svg_configuration"

class SVGConfigurationTest < Minitest::Test
Config = RNBottomTabs::SVGConfiguration
BASE = { "DEFINES_MODULE" => "YES" }.freeze

def test_unset_is_enabled_and_marked_as_defaulted
assert_nil Config.setting(global: nil, env: nil)
assert_nil Config.setting(global: nil, env: " ")
assert Config.enabled?(nil)
assert_equal(
"$(inherited) RN_BOTTOM_TABS_ENABLE_SVG=1 RN_BOTTOM_TABS_SVG_DEFAULTED=1",
Config.xcconfig(BASE, value: nil)["GCC_PREPROCESSOR_DEFINITIONS"]
)
end

def test_true_values_enable_svg
[true, "true", "1", "YES"].each do |value|
assert_equal true, Config.setting(global: value, env: nil), value.inspect
end
assert_equal true, Config.setting(global: nil, env: "1")
assert_equal(
"$(inherited) RN_BOTTOM_TABS_ENABLE_SVG=1",
Config.xcconfig(BASE, value: true)["GCC_PREPROCESSOR_DEFINITIONS"]
)
end

def test_false_values_disable_svg
[false, "false", "0", "no"].each do |value|
assert_equal false, Config.setting(global: value, env: nil), value.inspect
end
assert_equal false, Config.setting(global: nil, env: "0")
refute Config.enabled?(false)
assert_equal BASE, Config.xcconfig(BASE, value: false)
end

def test_podfile_value_wins_over_environment
assert_equal false, Config.setting(global: false, env: "1")
assert_equal true, Config.setting(global: true, env: "0")
end

def test_unsupported_value_raises
assert_raises(ArgumentError) { Config.setting(global: "maybe", env: nil) }
end

def test_warns_only_when_unset
Config.instance_variable_set(:@warned, false)
_, err = capture_io do
Config.warn_if_unset(nil)
Config.warn_if_unset(nil)
end
assert_equal 1, err.scan(/next major version/).size

_, err = capture_io do
Config.warn_if_unset(true)
Config.warn_if_unset(false)
end
assert_empty err
end
end
Loading
Loading