From ed62b7ceade6d468d2113e1f61ad6466f21e7b48 Mon Sep 17 00:00:00 2001 From: Christian Schmitz Date: Tue, 28 Nov 2023 21:47:03 +0100 Subject: [PATCH] feat: provide string format and wrapToString extensions --- README.md | 16 +- .../tynn/astring/ParcelableAStringTest.java | 129 +++++++-- ...ontextValueProvider.java => Provider.java} | 25 +- .../main/java/xyz/tynn/astring/Resource.java | 90 ++++++ .../xyz/tynn/astring/ResourceDelegate.java | 144 ---------- .../main/java/xyz/tynn/astring/ToString.java | 120 ++++++++ ...{CharSequenceWrapper.java => Wrapper.java} | 28 +- .../kotlin/xyz/tynn/astring/AStringFactory.kt | 143 ++++++++-- .../xyz/tynn/astring/AStringFactoryTest.java | 113 ++++++-- .../java/xyz/tynn/astring/AStringKtTest.java | 4 +- .../xyz/tynn/astring/AStringFactoryKtTest.kt | 262 ++++++++++++++++-- .../xyz/tynn/astring/AppIdProviderTest.kt | 16 +- .../tynn/astring/AppVersionProviderTest.kt | 16 +- .../{NullValueProviderTest.kt => NullTest.kt} | 34 ++- .../QuantityStringResourceDelegateTest.kt | 139 ---------- .../astring/QuantityStringResourceTest.kt | 142 ++++++++++ ...ateTest.kt => QuantityTextResourceTest.kt} | 36 +-- .../astring/StringResourceDelegateTest.kt | 131 --------- .../xyz/tynn/astring/StringResourceTest.kt | 134 +++++++++ ...rceDelegateTest.kt => TextResourceTest.kt} | 32 ++- .../kotlin/xyz/tynn/astring/ToStringTest.kt | 188 +++++++++++++ ...rSequenceWrapperTest.kt => WrapperTest.kt} | 24 +- build.gradle | 2 +- compose/build.gradle | 12 +- gradle/libs.versions.toml | 4 +- 25 files changed, 1358 insertions(+), 626 deletions(-) rename astring/src/main/java/xyz/tynn/astring/{ContextValueProvider.java => Provider.java} (62%) create mode 100644 astring/src/main/java/xyz/tynn/astring/Resource.java delete mode 100644 astring/src/main/java/xyz/tynn/astring/ResourceDelegate.java create mode 100644 astring/src/main/java/xyz/tynn/astring/ToString.java rename astring/src/main/java/xyz/tynn/astring/{CharSequenceWrapper.java => Wrapper.java} (62%) rename astring/src/test/kotlin/xyz/tynn/astring/{NullValueProviderTest.kt => NullTest.kt} (53%) delete mode 100644 astring/src/test/kotlin/xyz/tynn/astring/QuantityStringResourceDelegateTest.kt create mode 100644 astring/src/test/kotlin/xyz/tynn/astring/QuantityStringResourceTest.kt rename astring/src/test/kotlin/xyz/tynn/astring/{QuantityTextResourceDelegateTest.kt => QuantityTextResourceTest.kt} (56%) delete mode 100644 astring/src/test/kotlin/xyz/tynn/astring/StringResourceDelegateTest.kt create mode 100644 astring/src/test/kotlin/xyz/tynn/astring/StringResourceTest.kt rename astring/src/test/kotlin/xyz/tynn/astring/{TextResourceDelegateTest.kt => TextResourceTest.kt} (60%) create mode 100644 astring/src/test/kotlin/xyz/tynn/astring/ToStringTest.kt rename astring/src/test/kotlin/xyz/tynn/astring/{CharSequenceWrapperTest.kt => WrapperTest.kt} (61%) diff --git a/README.md b/README.md index 27b8616..00cb460 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,21 @@ There are several _standard_ implementations of `AString` for: * `getText()` and `getQuantityText()` delegates * the `getPackageName()` delegate - * the `versionName` resolver + * the `PackageInfo.versionName` resolver + + +### `AString` string format and `toString()` wrapper + +While `AString` represent any generic `CharSequence`, it might be useful to use +it as a `String` only or even use it as a format string. + + aString.format(1, "2") + aString.format(Locale.UK, 1, "2") + + aString.wrapToString() + +This behavior is similar to the approaches taken for `getText(int)`, +`getString(int)` and `getString(int, Object...)` string resource accessors. ### _Jetpack_ Compose diff --git a/astring/src/androidTest/java/xyz/tynn/astring/ParcelableAStringTest.java b/astring/src/androidTest/java/xyz/tynn/astring/ParcelableAStringTest.java index c5a7d65..e6ed6ea 100644 --- a/astring/src/androidTest/java/xyz/tynn/astring/ParcelableAStringTest.java +++ b/astring/src/androidTest/java/xyz/tynn/astring/ParcelableAStringTest.java @@ -7,73 +7,142 @@ import static xyz.tynn.astring.test.AStringAssert.assertParcelableAStringIdentity; import static xyz.tynn.astring.test.AStringAssert.assertParcelableAStringInvocation; +import android.content.Context; +import android.os.Parcel; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.junit.Test; import java.util.Date; +import java.util.Locale; public class ParcelableAStringTest { - public static final int RES_ID = 123; - public static final int QUANTITY = 456; - public static final Object[] FORMAT_ARGS = {"arg1", 2, 3L, 4.5, 6F, new Date()}; + private static final Locale LOCALE = Locale.UK; + private static final int RES_ID = 123; + private static final int QUANTITY = 456; + private static final AString FORMAT = new Format(); + private static final Object[] FORMAT_ARGS = {"arg1", 2, 3L, 4.5, 6F, new Date()}; + + @Test + public void Wrapper_should_implement_parcelable() { + assertParcelableAStringEquality(Wrapper.wrap("test-string")); + assertParcelableAStringInvocation(Wrapper.wrap("test-string")); + } + + @Test + public void Wrapper_of_null_should_implement_parcelable() { + assertParcelableAStringEquality(Wrapper.wrap(null)); + assertParcelableAStringInvocation(Wrapper.wrap(null)); + } + + @Test + public void Wrapper_NULL_should_implement_parcelable() { + assertParcelableAStringIdentity(Wrapper.NULL); + assertParcelableAStringInvocation(Wrapper.NULL); + } @Test - public void CharSequenceWrapper_should_implement_parcelable() { - assertParcelableAStringEquality(new CharSequenceWrapper("test-string")); - assertParcelableAStringInvocation(new CharSequenceWrapper("test-string")); + public void ToString_should_implement_parcelable() { + assertParcelableAStringEquality(ToString.wrap(FORMAT, LOCALE, FORMAT_ARGS)); + assertParcelableAStringInvocation(ToString.wrap(FORMAT, LOCALE, FORMAT_ARGS)); } @Test - public void CharSequenceWrapper_of_null_should_implement_parcelable() { - assertParcelableAStringEquality(new CharSequenceWrapper(null)); - assertParcelableAStringInvocation(new CharSequenceWrapper(null)); + public void ToString_without_locale_should_implement_parcelable() { + assertParcelableAStringEquality(ToString.wrap(FORMAT, null, FORMAT_ARGS)); + assertParcelableAStringInvocation(ToString.wrap(FORMAT, null, FORMAT_ARGS)); } @Test - public void CharSequenceWrapper_NULL_should_implement_parcelable() { - assertParcelableAStringIdentity(CharSequenceWrapper.NULL); - assertParcelableAStringInvocation(CharSequenceWrapper.NULL); + public void ToString_without_args_should_implement_parcelable() { + assertParcelableAStringEquality(ToString.wrap(FORMAT, LOCALE, null)); + assertParcelableAStringInvocation(ToString.wrap(FORMAT, LOCALE, null)); } @Test - public void ResourceDelegate_quantityString_should_implement_parcelable() { - assertParcelableAStringEquality(ResourceDelegate.quantityString(RES_ID, QUANTITY, FORMAT_ARGS)); + public void ToString_without_args_and_locale_should_implement_parcelable() { + assertParcelableAStringEquality(ToString.wrap(FORMAT, null, null)); + assertParcelableAStringInvocation(ToString.wrap(FORMAT, null, null)); } @Test - public void ResourceDelegate_quantityString_without_args_should_implement_parcelable() { - assertParcelableAStringEquality(ResourceDelegate.quantityString(RES_ID, QUANTITY, null)); + public void Resource_quantity_text_should_implement_parcelable() { + assertParcelableAStringEquality(Resource.wrap(RES_ID, QUANTITY)); } @Test - public void ResourceDelegate_quantityText_should_implement_parcelable() { - assertParcelableAStringEquality(ResourceDelegate.quantityText(RES_ID, QUANTITY)); + public void Resource_quantity_string_with_args_should_implement_parcelable() { + assertParcelableAStringEquality(Resource.wrap(RES_ID, QUANTITY, FORMAT_ARGS)); } @Test - public void ResourceDelegate_string_should_implement_parcelable() { - assertParcelableAStringEquality(ResourceDelegate.string(RES_ID, FORMAT_ARGS)); + public void Resource_quantity_string_without_args_should_implement_parcelable() { + assertParcelableAStringEquality(Resource.wrap(RES_ID, QUANTITY, null)); } @Test - public void ResourceDelegate_string_without_args_should_implement_parcelable() { - assertParcelableAStringEquality(ResourceDelegate.string(RES_ID, null)); + public void Resource_text_should_implement_parcelable() { + assertParcelableAStringEquality(Resource.wrap(RES_ID, null)); } @Test - public void ResourceDelegate_text_should_implement_parcelable() { - assertParcelableAStringEquality(ResourceDelegate.text(RES_ID)); + public void Resource_string_with_args_should_implement_parcelable() { + assertParcelableAStringEquality(Resource.wrap(RES_ID, null, FORMAT_ARGS)); } @Test - public void ContextValueProvider_AppIdProvider_should_implement_parcelable() { - assertParcelableAStringIdentity(ContextValueProvider.AppIdProvider); - assertParcelableAStringInvocation(ContextValueProvider.AppIdProvider); + public void Resource_string_without_args_should_implement_parcelable() { + assertParcelableAStringEquality(Resource.wrap(RES_ID, null, null)); } @Test - public void ContextValueProvider_AppVersionProvider_should_implement_parcelable() { - assertParcelableAStringIdentity(ContextValueProvider.AppVersionProvider); - assertParcelableAStringInvocation(ContextValueProvider.AppVersionProvider); + public void AppIdProvider_should_implement_parcelable() { + assertParcelableAStringIdentity(Provider.AppIdProvider); + assertParcelableAStringInvocation(Provider.AppIdProvider); + } + + @Test + public void AppVersionProvider_should_implement_parcelable() { + assertParcelableAStringIdentity(Provider.AppVersionProvider); + assertParcelableAStringInvocation(Provider.AppVersionProvider); + } + + private static class Format implements AString { + + @Nullable + @Override + public String invoke(@NonNull Context context) { + return "%s%d%d%f%f%s"; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof Format; + } + + @Override + public int hashCode() { + return 43; + } + + public static final Creator CREATOR = new Creator<>() { + + @Override + public Format createFromParcel(Parcel source) { + return new Format(); + } + + @Override + public Format[] newArray(int size) { + return new Format[size]; + } + }; } } diff --git a/astring/src/main/java/xyz/tynn/astring/ContextValueProvider.java b/astring/src/main/java/xyz/tynn/astring/Provider.java similarity index 62% rename from astring/src/main/java/xyz/tynn/astring/ContextValueProvider.java rename to astring/src/main/java/xyz/tynn/astring/Provider.java index d1cb6e3..66ed945 100644 --- a/astring/src/main/java/xyz/tynn/astring/ContextValueProvider.java +++ b/astring/src/main/java/xyz/tynn/astring/Provider.java @@ -3,10 +3,6 @@ package xyz.tynn.astring; -import static android.content.pm.PackageManager.PackageInfoFlags.of; -import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.TIRAMISU; - import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -14,14 +10,14 @@ import androidx.annotation.NonNull; -enum ContextValueProvider implements AString { +enum Provider implements AString { /** * Implementation providing {@link Context#getPackageName()} */ AppIdProvider { @Override - public CharSequence invoke(@NonNull Context context) { + public String invoke(@NonNull Context context) { return context.getPackageName(); } @@ -37,14 +33,11 @@ public String toString() { */ AppVersionProvider { @Override - @SuppressWarnings("deprecation") - public CharSequence invoke(@NonNull Context context) { + public String invoke(@NonNull Context context) { try { String packageName = context.getPackageName(); PackageManager packageManager = context.getPackageManager(); - if (SDK_INT < TIRAMISU) - return packageManager.getPackageInfo(packageName, 0).versionName; - return packageManager.getPackageInfo(packageName, of(0)).versionName; + return packageManager.getPackageInfo(packageName, 0).versionName; } catch (PackageManager.NameNotFoundException e) { throw new IllegalStateException(e); } @@ -64,16 +57,16 @@ public void writeToParcel(@NonNull Parcel parcel, int i) { parcel.writeString(name()); } - public static final Creator CREATOR = new Creator<>() { + public static final Creator CREATOR = new Creator<>() { @Override - public ContextValueProvider createFromParcel(Parcel source) { - return ContextValueProvider.valueOf(source.readString()); + public Provider createFromParcel(Parcel source) { + return Provider.valueOf(source.readString()); } @Override - public ContextValueProvider[] newArray(int size) { - return new ContextValueProvider[size]; + public Provider[] newArray(int size) { + return new Provider[size]; } }; } diff --git a/astring/src/main/java/xyz/tynn/astring/Resource.java b/astring/src/main/java/xyz/tynn/astring/Resource.java new file mode 100644 index 0000000..abb94ed --- /dev/null +++ b/astring/src/main/java/xyz/tynn/astring/Resource.java @@ -0,0 +1,90 @@ +// Copyright 2023 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring; + +import static android.content.res.Resources.ID_NULL; +import static androidx.core.os.ParcelCompat.readBoolean; +import static androidx.core.os.ParcelCompat.writeBoolean; +import static xyz.tynn.astring.Wrapper.NULL; + +import android.content.Context; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +/** + * Implementation delegating to a resource with or without format arguments + */ +final class Resource implements AString { + + private final int resId; + private final Integer quantity; + + private Resource(int resId, Integer quantity) { + this.resId = resId; + this.quantity = quantity; + } + + static AString wrap(int resId, Integer quantity) { + if (resId == ID_NULL) return NULL; + return new Resource(resId, quantity); + } + + static AString wrap(int resId, Integer quantity, Object[] formatArgs) { + if (resId == ID_NULL) return NULL; + return ToString.wrap(new Resource(resId, quantity), null, formatArgs); + } + + @Override + public CharSequence invoke(@NonNull Context context) { + return quantity == null ? context.getText(resId) : + context.getResources().getQuantityText(resId, quantity); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Resource that = (Resource) o; + return resId == that.resId && Objects.equals(quantity, that.quantity); + } + + @Override + public int hashCode() { + return Objects.hash(resId, quantity); + } + + @NonNull + @Override + public String toString() { + StringBuilder sb = new StringBuilder("AString("); + if (quantity != null) sb.append("Quantity"); + sb.append("TextResource(").append(resId); + if (quantity != null) sb.append(',').append(quantity); + return sb.append("))").toString(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(resId); + writeBoolean(dest, quantity != null); + if (quantity != null) dest.writeInt(quantity); + } + + public static final Creator CREATOR = new Creator<>() { + + @Override + public Resource createFromParcel(Parcel source) { + return new Resource(source.readInt(), + readBoolean(source) ? source.readInt() : null); + } + + @Override + public Resource[] newArray(int size) { + return new Resource[size]; + } + }; +} diff --git a/astring/src/main/java/xyz/tynn/astring/ResourceDelegate.java b/astring/src/main/java/xyz/tynn/astring/ResourceDelegate.java deleted file mode 100644 index ac6d014..0000000 --- a/astring/src/main/java/xyz/tynn/astring/ResourceDelegate.java +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2022 Christian Schmitz -// SPDX-License-Identifier: Apache-2.0 - -package xyz.tynn.astring; - -import static androidx.core.os.ConfigurationCompat.getLocales; -import static androidx.core.os.ParcelCompat.readBoolean; -import static androidx.core.os.ParcelCompat.writeBoolean; -import static java.lang.Integer.MIN_VALUE; -import static java.lang.String.format; - -import android.content.Context; -import android.content.res.Resources; -import android.os.Parcel; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.PluralsRes; -import androidx.annotation.StringRes; - -import java.util.Arrays; -import java.util.Locale; -import java.util.Objects; - -/** - * Implementation delegating to a resource with or without format arguments - */ -final class ResourceDelegate implements AString { - - private final boolean isPlural, isText; - private final int resId, quantity; - private final Object[] formatArgs; - - private ResourceDelegate( - boolean isPlural, boolean isText, - int resId, int quantity, Object[] formatArgs) { - this.isPlural = isPlural; - this.isText = isText; - this.resId = resId; - this.quantity = quantity; - this.formatArgs = formatArgs == null || formatArgs.length == 0 ? null : formatArgs; - } - - @Override - public CharSequence invoke(@NonNull Context context) { - Resources resources = context.getResources(); - CharSequence value = isPlural - ? resources.getQuantityText(resId, quantity) - : resources.getText(resId); - return isText ? value : formatArgs == null ? value.toString() - : format(getLocale(resources), value.toString(), formatArgs); - } - - private static Locale getLocale(Resources resources) { - return getLocales(resources.getConfiguration()).get(0); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ResourceDelegate that = (ResourceDelegate) o; - if (isPlural != that.isPlural) return false; - if (isText != that.isText) return false; - if (resId != that.resId) return false; - if (quantity != that.quantity) return false; - return Arrays.equals(formatArgs, that.formatArgs); - } - - @Override - public int hashCode() { - return 31 * Objects.hash(isPlural, isText, resId, quantity) - + Arrays.hashCode(formatArgs); - } - - @NonNull - @Override - public String toString() { - StringBuilder sb = new StringBuilder("AString("); - if (isPlural) sb.append("Quantity"); - sb.append(isText ? "Text" : "String"); - sb.append("Resource(").append(resId); - if (isPlural) sb.append(',').append(quantity); - if (formatArgs != null) for (Object o : formatArgs) - sb.append(',').append(o); - return sb.append("))").toString(); - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - writeBoolean(dest, isPlural); - writeBoolean(dest, isText); - dest.writeInt(resId); - dest.writeInt(quantity); - dest.writeValue(formatArgs); - } - - /** - * Implementation delegating to a quantity string resource - * with or without format arguments - */ - static ResourceDelegate quantityString( - @PluralsRes int resId, int quantity, @Nullable Object[] formatArgs) { - return new ResourceDelegate(true, false, resId, quantity, formatArgs); - } - - /** - * Implementation delegating to a quantity text resource - */ - static ResourceDelegate quantityText(@PluralsRes int resId, int quantity) { - return new ResourceDelegate(true, true, resId, quantity, null); - } - - /** - * Implementation delegating to a string resource - * with or without format arguments - */ - static ResourceDelegate string(@StringRes int resId, @Nullable Object[] formatArgs) { - return new ResourceDelegate(false, false, resId, MIN_VALUE, formatArgs); - } - - /** - * Implementation delegating to a text resource - */ - static ResourceDelegate text(@StringRes int resId) { - return new ResourceDelegate(false, true, resId, MIN_VALUE, null); - } - - public static final Creator CREATOR = new Creator<>() { - - @Override - public ResourceDelegate createFromParcel(Parcel source) { - return new ResourceDelegate( - readBoolean(source), readBoolean(source), - source.readInt(), source.readInt(), - (Object[]) source.readValue(getClass().getClassLoader())); - } - - @Override - public ResourceDelegate[] newArray(int size) { - return new ResourceDelegate[size]; - } - }; -} diff --git a/astring/src/main/java/xyz/tynn/astring/ToString.java b/astring/src/main/java/xyz/tynn/astring/ToString.java new file mode 100644 index 0000000..5acd157 --- /dev/null +++ b/astring/src/main/java/xyz/tynn/astring/ToString.java @@ -0,0 +1,120 @@ +// Copyright 2023 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring; + +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.N; +import static androidx.core.os.ParcelCompat.readParcelable; +import static androidx.core.os.ParcelCompat.readSerializable; +import static java.lang.String.format; +import static xyz.tynn.astring.Wrapper.NULL; + +import android.content.Context; +import android.content.res.Configuration; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Objects; + +/** + * Implementation calling {@link CharSequence#toString()} on the result and + * {@link String#format(Locale, String, Object...)} with provided format arguments + */ +final class ToString implements AString { + + private final AString delegate; + private final Locale locale; + private final Object[] formatArgs; + + private ToString(AString delegate, Locale locale, Object[] formatArgs) { + this.delegate = delegate; + this.locale = locale; + this.formatArgs = formatArgs; + } + + static AString wrap(AString delegate, Locale locale, Object[] formatArgs) { + if (delegate == null || delegate == NULL) return NULL; + if (formatArgs == null || formatArgs.length == 0) { + if (delegate instanceof Provider || delegate instanceof ToString) return delegate; + if (delegate instanceof Wrapper) return ((Wrapper) delegate).wrapToString(); + locale = null; + formatArgs = null; + } else if (delegate instanceof ToString) { + ToString toString = (ToString) delegate; + if (toString.formatArgs == null) delegate = toString.delegate; + } + return new ToString(delegate, locale, formatArgs); + } + + @Override + public String invoke(@NonNull Context context) { + CharSequence formatString = delegate.invoke(context); + return formatString == null ? null : formatArgs == null ? formatString.toString() + : format(getLocale(context), formatString.toString(), formatArgs); + } + + @SuppressWarnings("deprecation") + private Locale getLocale(Context context) { + if (locale != null) return locale; + Configuration configuration = context.getResources().getConfiguration(); + if (SDK_INT < N) return configuration.locale; + return configuration.getLocales().get(0); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ToString that = (ToString) o; + return delegate.equals(that.delegate) && + Objects.equals(this.locale, that.locale) && + Arrays.equals(this.formatArgs, that.formatArgs); + } + + @Override + public int hashCode() { + return Objects.hash(delegate, locale, Arrays.hashCode(formatArgs)); + } + + @NonNull + @Override + public String toString() { + StringBuilder sb = new StringBuilder("AString("); + if (formatArgs != null) { + sb.append("Format("); + if (locale != null) sb.append(locale).append(','); + sb.append(delegate); + for (Object o : formatArgs) sb.append(',').append(o); + } else { + sb.append("String(").append(delegate); + } + return sb.append("))").toString(); + } + + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(this.delegate, flags); + dest.writeSerializable(locale); + dest.writeValue(formatArgs); + } + + public static final Creator CREATOR = new Creator<>() { + + @Override + public ToString createFromParcel(Parcel source) { + ClassLoader classLoader = getClass().getClassLoader(); + return new ToString( + readParcelable(source, classLoader, AString.class), + readSerializable(source, classLoader, Locale.class), + (Object[]) source.readValue(classLoader)); + } + + @Override + public ToString[] newArray(int size) { + return new ToString[size]; + } + }; +} diff --git a/astring/src/main/java/xyz/tynn/astring/CharSequenceWrapper.java b/astring/src/main/java/xyz/tynn/astring/Wrapper.java similarity index 62% rename from astring/src/main/java/xyz/tynn/astring/CharSequenceWrapper.java rename to astring/src/main/java/xyz/tynn/astring/Wrapper.java index adc0da9..c38f7ee 100644 --- a/astring/src/main/java/xyz/tynn/astring/CharSequenceWrapper.java +++ b/astring/src/main/java/xyz/tynn/astring/Wrapper.java @@ -17,16 +17,20 @@ /** * Implementation wrapping a {@link CharSequence} */ -final class CharSequenceWrapper implements AString { +final class Wrapper implements AString { - static final CharSequenceWrapper NULL = new CharSequenceWrapper(null); + static final Wrapper NULL = new Wrapper(null); private final CharSequence value; - CharSequenceWrapper(@Nullable CharSequence value) { + private Wrapper(@Nullable CharSequence value) { this.value = value; } + static Wrapper wrap(CharSequence value) { + return value == null ? NULL : new Wrapper(value); + } + @Override public CharSequence invoke(@Nullable Context context) { return value; @@ -36,7 +40,7 @@ public CharSequence invoke(@Nullable Context context) { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - CharSequenceWrapper that = (CharSequenceWrapper) o; + Wrapper that = (Wrapper) o; return Objects.equals(value, that.value); } @@ -56,17 +60,21 @@ public void writeToParcel(@NonNull Parcel dest, int flags) { TextUtils.writeToParcel(value, dest, flags); } - public static final Creator CREATOR = new Creator<>() { + Wrapper wrapToString() { + CharSequence value = this.value; + return value == null || value instanceof String ? this : new Wrapper(value.toString()); + } + + public static final Creator CREATOR = new Creator<>() { @Override - public CharSequenceWrapper createFromParcel(Parcel source) { - CharSequence value = CHAR_SEQUENCE_CREATOR.createFromParcel(source); - return value == null ? NULL : new CharSequenceWrapper(value); + public Wrapper createFromParcel(Parcel source) { + return wrap(CHAR_SEQUENCE_CREATOR.createFromParcel(source)); } @Override - public CharSequenceWrapper[] newArray(int size) { - return new CharSequenceWrapper[size]; + public Wrapper[] newArray(int size) { + return new Wrapper[size]; } }; } diff --git a/astring/src/main/kotlin/xyz/tynn/astring/AStringFactory.kt b/astring/src/main/kotlin/xyz/tynn/astring/AStringFactory.kt index 3dcd930..dc578fd 100644 --- a/astring/src/main/kotlin/xyz/tynn/astring/AStringFactory.kt +++ b/astring/src/main/kotlin/xyz/tynn/astring/AStringFactory.kt @@ -8,19 +8,66 @@ package xyz.tynn.astring import android.content.Context import android.content.pm.PackageInfo +import android.content.res.Configuration import android.content.res.Resources.ID_NULL import android.os.Parcel import android.text.TextUtils import androidx.annotation.PluralsRes import androidx.annotation.StringRes -import xyz.tynn.astring.ContextValueProvider.AppIdProvider -import xyz.tynn.astring.ContextValueProvider.AppVersionProvider +import xyz.tynn.astring.Provider.AppIdProvider +import xyz.tynn.astring.Provider.AppVersionProvider +import xyz.tynn.astring.Wrapper.NULL +import java.util.Locale /** * An `AString` always providing `null` */ @JvmField -public val nullAsAString: AString = CharSequenceWrapper.NULL +public val nullAsAString: AString = NULL + +/** + * Returns [nullAsAString] + * + * This ensures that `null.asAString()` doesn't produce an overload ambiguity + */ +@[JvmSynthetic Suppress("NOTHING_TO_INLINE", "UnusedReceiverParameter")] +public inline fun Nothing?.asAString(): AString = nullAsAString + +/** + * Returns [nullAsAString] + * + * This ensures that `AString(null)` doesn't produce an overload ambiguity + */ +@[JvmSynthetic Suppress("NOTHING_TO_INLINE", "UNUSED_PARAMETER")] +public inline fun AString( + nothing: Nothing?, +): AString = nullAsAString + +/** + * Returns [nullAsAString] when this [AString] is null + * + * This is a shorthand for + * ``` + * this ?: nullAsAString + * ``` + */ +@[JvmSynthetic Suppress("NOTHING_TO_INLINE")] +public inline fun AString?.asAString(): AString = AString( + aString = this, +) + +/** + * Returns [nullAsAString] when [aString] is null + * + * This is a shorthand for + * ``` + * aString ?: nullAsAString + * ``` + */ +@JvmName("wrapNullAsAString") +public fun AString( + aString: AString?, +): AString = aString ?: nullAsAString /** * Creates an `AString` from a `CharSequence?` @@ -28,10 +75,12 @@ public val nullAsAString: AString = CharSequenceWrapper.NULL * Returns [nullAsAString] for null * * **Note** that [TextUtils.writeToParcel] is used to parcel - * the [CharSequence] which might lose some custom styles + * the [CharSequence] which might lose some custom spans */ @[JvmSynthetic Suppress("NOTHING_TO_INLINE")] -public inline fun CharSequence?.asAString(): AString = AString(this) +public inline fun CharSequence?.asAString(): AString = AString( + value = this +) /** * Creates an `AString` from a `CharSequence?` @@ -39,14 +88,14 @@ public inline fun CharSequence?.asAString(): AString = AString(this) * Returns [nullAsAString] for null * * **Note** that [TextUtils.writeToParcel] is used to parcel - * the [CharSequence] which might lose some custom styles + * the [CharSequence] which might lose some custom spans */ @JvmName("createFromCharSequence") public fun AString( value: CharSequence?, -): AString = if (value == null) - nullAsAString -else CharSequenceWrapper(value) +): AString = Wrapper.wrap( + value, +) /** * Creates an `AString` from a plurals string resource @@ -57,9 +106,7 @@ else CharSequenceWrapper(value) public fun QuantityStringResource( @PluralsRes resId: Int, quantity: Int, -): AString = if (resId == ID_NULL) - nullAsAString -else ResourceDelegate.quantityString( +): AString = Resource.wrap( resId, quantity, null, @@ -78,9 +125,7 @@ public fun QuantityStringResource( @PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?, -): AString = if (resId == ID_NULL) - nullAsAString -else ResourceDelegate.quantityString( +): AString = Resource.wrap( resId, quantity, formatArgs, @@ -95,9 +140,7 @@ else ResourceDelegate.quantityString( public fun QuantityTextResource( @PluralsRes resId: Int, quantity: Int, -): AString = if (resId == ID_NULL) - nullAsAString -else ResourceDelegate.quantityText( +): AString = Resource.wrap( resId, quantity, ) @@ -110,11 +153,10 @@ else ResourceDelegate.quantityText( @JvmName("createFromStringResource") public fun StringResource( @StringRes resId: Int, -): AString = if (resId == ID_NULL) - nullAsAString -else ResourceDelegate.string( +): AString = Resource.wrap( resId, null, + null, ) /** @@ -129,10 +171,9 @@ else ResourceDelegate.string( public fun StringResource( @StringRes resId: Int, vararg formatArgs: Any?, -): AString = if (resId == ID_NULL) - nullAsAString -else ResourceDelegate.string( +): AString = Resource.wrap( resId, + null, formatArgs, ) @@ -144,10 +185,58 @@ else ResourceDelegate.string( @JvmName("createFromTextResource") public fun TextResource( @StringRes resId: Int, -): AString = if (resId == ID_NULL) - nullAsAString -else ResourceDelegate.text( +): AString = Resource.wrap( resId, + null, +) + +/** + * Wraps the [AString] to format the string with [formatArgs] + * using the first locale of [Configuration.getLocales] + * + * Returns [nullAsAString] if this [AString] is `null` or [nullAsAString] + * + * @see String.format + */ +@JvmName("formatWithAString") +public fun AString?.format( + vararg formatArgs: Any?, +): AString = ToString.wrap( + this, + null, + formatArgs, +) + +/** + * Wraps the [AString] to format the string with [formatArgs] + * using the provided [locale] if not null or the first locale of + * [Configuration.getLocales] + * + * Returns [nullAsAString] if this [AString] is `null` or [nullAsAString] + * + * @see String.format + */ +@JvmName("formatWithAString") +public fun AString?.format( + locale: Locale?, + vararg formatArgs: Any?, +): AString = ToString.wrap( + this, + locale, + formatArgs, +) + +/** + * Wraps the [AString] to always return a [String] on invocation + * + * Returns [nullAsAString] if this [AString] is `null` or [nullAsAString] + * + * @see CharSequence.toString + */ +public fun AString?.wrapToString(): AString = ToString.wrap( + this, + null, + null, ) /** diff --git a/astring/src/test/java/xyz/tynn/astring/AStringFactoryTest.java b/astring/src/test/java/xyz/tynn/astring/AStringFactoryTest.java index 57c573b..085f31d 100644 --- a/astring/src/test/java/xyz/tynn/astring/AStringFactoryTest.java +++ b/astring/src/test/java/xyz/tynn/astring/AStringFactoryTest.java @@ -8,22 +8,35 @@ import org.junit.Test; +import java.util.Locale; + public class AStringFactoryTest { @Test - public void nullAsAString_should_return_NullValueProvider() { - assertSame(AStringFactory.nullAsAString, AStringFactory.nullAsAString); + public void nullAsAString_should_return_NULL() { + assertSame(Wrapper.NULL, AStringFactory.nullAsAString); + } + + @Test + public void wrapNullAsAString_should_return_NULL() { + assertSame(Wrapper.NULL, AStringFactory.wrapNullAsAString(null)); + } + + @Test + public void wrapNullAsAString_should_return_identity() { + AString aString = AStringFactory.createFromQuantityStringResource(1, 2); + assertSame(aString, AStringFactory.wrapNullAsAString(aString)); } @Test - public void createFromCharSequence_should_return_NullValueProvider() { - assertSame(AStringFactory.nullAsAString, AStringFactory.createFromCharSequence(null)); + public void createFromCharSequence_should_return_NULL() { + assertSame(Wrapper.NULL, AStringFactory.createFromCharSequence(null)); } @Test - public void createFromQuantityStringResource_should_return_NullValueProvider() { - assertSame(AStringFactory.nullAsAString, AStringFactory.createFromQuantityStringResource(0, 1)); - assertSame(AStringFactory.nullAsAString, AStringFactory.createFromQuantityStringResource(0, 1, 2)); + public void createFromQuantityStringResource_should_return_NULL() { + assertSame(Wrapper.NULL, AStringFactory.createFromQuantityStringResource(0, 1)); + assertSame(Wrapper.NULL, AStringFactory.createFromQuantityStringResource(0, 1, 2)); } @Test @@ -33,14 +46,14 @@ public void createFromQuantityStringResource_with_null_args_should_return_Quanti } @Test - public void createFromQuantityTextResource_should_return_NullValueProvider() { - assertSame(AStringFactory.nullAsAString, AStringFactory.createFromQuantityTextResource(0, 1)); + public void createFromQuantityTextResource_should_return_NULL() { + assertSame(Wrapper.NULL, AStringFactory.createFromQuantityTextResource(0, 1)); } @Test - public void createFromStringResource_should_return_NullValueProvider() { - assertSame(AStringFactory.nullAsAString, AStringFactory.createFromStringResource(0)); - assertSame(AStringFactory.nullAsAString, AStringFactory.createFromStringResource(0, 1)); + public void createFromStringResource_should_return_NULL() { + assertSame(Wrapper.NULL, AStringFactory.createFromStringResource(0)); + assertSame(Wrapper.NULL, AStringFactory.createFromStringResource(0, 1)); } @Test @@ -50,17 +63,85 @@ public void createFromStringResource_with_null_args_should_return_StringResource } @Test - public void createFromTextResource_should_return_NullValueProvider() { - assertSame(AStringFactory.nullAsAString, AStringFactory.createFromTextResource(0)); + public void createFromTextResource_should_return_NULL() { + assertSame(Wrapper.NULL, AStringFactory.createFromTextResource(0)); + } + + @Test + public void format_should_return_ToString() { + assertEquals(AStringFactory.formatWithAString(Wrapper.NULL, 1, "2"), + AStringFactory.formatWithAString(Wrapper.NULL, 1, "2")); + } + + @Test + public void format_without_args_should_return_ToString() { + assertEquals(AStringFactory.formatWithAString(Wrapper.NULL), + AStringFactory.formatWithAString(Wrapper.NULL, (Object[]) null)); + } + + @Test + public void format_should_be_NULL_on_null_format() { + assertSame(Wrapper.NULL, AStringFactory.formatWithAString(null, 1, "2")); + assertSame(Wrapper.NULL, AStringFactory.formatWithAString(Wrapper.NULL, 1, "2")); + } + + @Test + public void format_without_locale_should_return_ToString() { + assertEquals(AStringFactory.formatWithAString(Wrapper.NULL, 1, "2"), + AStringFactory.formatWithAString(Wrapper.NULL, null, 1, "2")); + } + + @Test + public void format_without_locale_and_args_should_return_ToString() { + assertEquals(AStringFactory.formatWithAString(Wrapper.NULL), + AStringFactory.formatWithAString(Wrapper.NULL, null, (Object[]) null)); + } + + @Test + public void format_with_locale_should_return_ToString() { + assertEquals(AStringFactory.formatWithAString(Wrapper.NULL, Locale.UK, 1, "2"), + AStringFactory.formatWithAString(Wrapper.NULL, Locale.UK, 1, "2")); + } + + @Test + public void format_with_locale_should_be_NULL_on_null_format() { + assertEquals(Wrapper.NULL, AStringFactory.formatWithAString(null, Locale.UK, 1, "2")); + assertEquals(Wrapper.NULL, AStringFactory.formatWithAString(Wrapper.NULL, Locale.UK, 1, "2")); + } + + @Test + public void wrapToString_should_be_NULL_on_null() { + assertEquals(Wrapper.NULL, AStringFactory.wrapToString(null)); + assertEquals(Wrapper.NULL, AStringFactory.wrapToString(Wrapper.NULL)); + } + + @Test + public void wrapToString_should_be_identity_on_Provider() { + assertSame(Provider.AppIdProvider, AStringFactory.wrapToString(Provider.AppIdProvider)); + assertSame(Provider.AppVersionProvider, AStringFactory.wrapToString(Provider.AppVersionProvider)); + } + + @Test + public void wrapToString_should_be_identity_on_ToString() { + AString aString = AStringFactory.formatWithAString(Provider.AppIdProvider, 1); + assertSame(aString, AStringFactory.wrapToString(aString)); + } + + @Test + public void wrapToString_should_be_Wrapper_on_Wrapper() { + AString aString = AStringFactory.createFromCharSequence("value"); + assertSame(aString, AStringFactory.wrapToString(aString)); + assertEquals(aString, AStringFactory.wrapToString( + AStringFactory.createFromCharSequence(new StringBuilder("value")))); } @Test public void appIdAString_should_return_AppIdProvider() { - assertSame(ContextValueProvider.AppIdProvider, AStringFactory.appIdAString); + assertSame(Provider.AppIdProvider, AStringFactory.appIdAString); } @Test public void appVersionAString_should_return_AppVersionProvider() { - assertSame(ContextValueProvider.AppVersionProvider, AStringFactory.appVersionAString); + assertSame(Provider.AppVersionProvider, AStringFactory.appVersionAString); } } diff --git a/astring/src/test/java/xyz/tynn/astring/AStringKtTest.java b/astring/src/test/java/xyz/tynn/astring/AStringKtTest.java index b169053..b473e1d 100644 --- a/astring/src/test/java/xyz/tynn/astring/AStringKtTest.java +++ b/astring/src/test/java/xyz/tynn/astring/AStringKtTest.java @@ -46,7 +46,7 @@ public void invokeWithContext_should_delegate_null_to_context() { assertNull(AStringKt.invokeWithContext(context, null)); } - @SuppressWarnings("ConstantConditions") + @SuppressWarnings({"ConstantConditions", "RedundantCast"}) @Test(expected = NullPointerException.class) public void invokeWithContext_should_throw_on_null_context() { AStringKt.invokeWithContext((Context) null, aString); @@ -90,7 +90,7 @@ public void invokeWithView_should_delegate_null_to_view() { assertNull(AStringKt.invokeWithView(view, null)); } - @SuppressWarnings("ConstantConditions") + @SuppressWarnings({"ConstantConditions", "RedundantCast"}) @Test(expected = NullPointerException.class) public void invokeWithView_should_throw_on_null_view() { AStringKt.invokeWithView((View) null, aString); diff --git a/astring/src/test/kotlin/xyz/tynn/astring/AStringFactoryKtTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/AStringFactoryKtTest.kt index ca83f1e..6759461 100644 --- a/astring/src/test/kotlin/xyz/tynn/astring/AStringFactoryKtTest.kt +++ b/astring/src/test/kotlin/xyz/tynn/astring/AStringFactoryKtTest.kt @@ -4,6 +4,8 @@ package xyz.tynn.astring import android.content.res.Resources.ID_NULL +import io.mockk.mockk +import java.util.Locale import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertSame @@ -11,117 +13,321 @@ import kotlin.test.assertSame internal class AStringFactoryKtTest { @Test - fun `nullAsAString should be the NullValueProvider singleton`() { + fun `nullAsAString should be the NULL singleton`() { assertSame( - nullAsAString, + Wrapper.NULL, nullAsAString, ) } @Test - fun `asAString should return NullValueProvider for null`() { + fun `asAString should return NULL for null`() { assertSame( - nullAsAString, + Wrapper.NULL, null.asAString(), ) } @Test - fun `asAString should return CharSequenceWrapper for CharSequence`() { + fun `AString should return NULL for null`() { + assertSame( + Wrapper.NULL, + AString(null), + ) + assertSame( + Wrapper.NULL, + AString(nothing = null), + ) + } + + @Test + fun `asAString should return NULL for null AString`() { + val aString: AString? = null + assertSame( + Wrapper.NULL, + aString.asAString(), + ) + } + + @Test + fun `asAString should be an identity for AString`() { + val aString: AString = mockk() + assertSame( + aString, + aString.asAString(), + ) + } + + @Test + fun `AString should return NULL for null AString`() { + val aString: AString? = null + assertSame( + Wrapper.NULL, + AString(aString), + ) + assertSame( + Wrapper.NULL, + AString(aString = null), + ) + } + + @Test + fun `AString should be an identity for AString`() { + val aString: AString = mockk() + assertSame( + aString, + AString(aString), + ) + } + + @Test + fun `asAString should return Null for null CharSequence`() { + val charSequence: CharSequence? = null + assertEquals( + Wrapper.NULL, + charSequence.asAString(), + ) + } + + @Test + fun `asAString should return Wrapper for CharSequence`() { assertEquals( - CharSequenceWrapper("foo"), + Wrapper.wrap("foo"), "foo".asAString(), ) } @Test - fun `StringResource should return NullValueProvider for ID_NULL`() { + fun `AString should return NULL for null CharSequence`() { + val charSequence: CharSequence? = null + assertEquals( + Wrapper.NULL, + AString(charSequence), + ) + assertEquals( + Wrapper.NULL, + AString(value = null), + ) + } + + @Test + fun `AString should return Wrapper for CharSequence`() { + assertEquals( + Wrapper.wrap("foo"), + AString("foo"), + ) + } + + @Test + fun `StringResource should return NULL for ID_NULL`() { assertSame( - nullAsAString, + Wrapper.NULL, StringResource(ID_NULL), ) assertSame( - nullAsAString, + Wrapper.NULL, StringResource(ID_NULL, 1), ) } @Test - fun `StringResource should return StringResourceDelegate without format args`() { + fun `StringResource should return ToString Resource without format args`() { assertEquals( - ResourceDelegate.string(1, arrayOf()), + ToString.wrap( + Resource.wrap(1, null), + null, + arrayOf(), + ), StringResource(1), ) } @Test - fun `StringResource should return StringResourceDelegate with format args`() { + fun `StringResource should return ToString Resource with format args`() { assertEquals( - ResourceDelegate.string(1, arrayOf(2, "3")), + ToString.wrap( + Resource.wrap(1, null), + null, + arrayOf(2, "3"), + ), StringResource(1, 2, "3"), ) } @Test - fun `TextResource should return NullValueProvider for ID_NULL`() { + fun `TextResource should return NULL for ID_NULL`() { assertSame( - nullAsAString, + Wrapper.NULL, TextResource(ID_NULL), ) } @Test - fun `TextResource should return TextResourceDelegate`() { + fun `TextResource should return Resource`() { assertEquals( - ResourceDelegate.text(1), + Resource.wrap(1, null), TextResource(1), ) } @Test - fun `QuantityStringResource should return NullValueProvider for ID_NULL`() { + fun `QuantityStringResource should return NULL for ID_NULL`() { assertSame( - nullAsAString, + Wrapper.NULL, QuantityStringResource(ID_NULL, 1), ) assertSame( - nullAsAString, + Wrapper.NULL, QuantityStringResource(ID_NULL, 1, 2), ) } @Test - fun `QuantityStringResource should return QuantityStringResourceDelegate without format args`() { + fun `QuantityStringResource should return ToString Resource quantity without format args`() { assertEquals( - ResourceDelegate.quantityString(1, 2, arrayOf()), + ToString.wrap( + Resource.wrap(1, 2), + null, + arrayOf(), + ), QuantityStringResource(1, 2), ) } @Test - fun `QuantityStringResource should return QuantityStringResourceDelegate with format args`() { + fun `QuantityStringResource should return ToString Resource quantity with format args`() { assertEquals( - ResourceDelegate.quantityString(1, 2, arrayOf(3, "4")), + ToString.wrap( + Resource.wrap(1, 2), + null, + arrayOf(3, "4"), + ), QuantityStringResource(1, 2, 3, "4"), ) } @Test - fun `QuantityTextResource should return NullValueProvider for ID_NULL`() { + fun `QuantityTextResource should return NULL for ID_NULL`() { assertSame( - nullAsAString, + Wrapper.NULL, QuantityTextResource(ID_NULL, 1), ) } @Test - fun `QuantityTextResource should return QuantityTextResourceDelegate`() { + fun `QuantityTextResource should return Resource quantity`() { assertEquals( - ResourceDelegate.quantityText(1, 2), + Resource.wrap(1, 2), QuantityTextResource(1, 2), ) } + @Test + fun `format should return ToString`() { + assertEquals( + ToString.wrap(TextResource(1), null, arrayOf(1, "2")), + TextResource(1).format(1, "2"), + ) + assertEquals( + ToString.wrap(TextResource(1), Locale.GERMAN, arrayOf(1, "2")), + TextResource(1).format(Locale.GERMAN, 1, "2"), + ) + assertEquals( + ToString.wrap(TextResource(1), null, arrayOf(1, "2")), + TextResource(1).format(locale = null, 1, "2"), + ) + } + + @Test + fun `format should return ToString with format args for ToString`() { + assertEquals( + ToString.wrap(TextResource(1), null, arrayOf(1, "2")), + TextResource(1).wrapToString().format(1, "2"), + ) + assertEquals( + ToString.wrap(TextResource(1), Locale.GERMAN, arrayOf(1, "2")), + TextResource(1).wrapToString().format(Locale.GERMAN, 1, "2"), + ) + assertEquals( + ToString.wrap(TextResource(1), null, arrayOf(1, "2")), + TextResource(1).wrapToString().format(locale = null, 1, "2"), + ) + } + + @Test + fun `format should return ToString ignoring empty format args`() { + assertEquals( + ToString.wrap(TextResource(1), null, null), + TextResource(1).format(), + ) + assertEquals( + ToString.wrap(TextResource(1), null, null), + TextResource(1).format(Locale.GERMAN), + ) + assertEquals( + ToString.wrap(TextResource(1), null, null), + TextResource(1).format(locale = null), + ) + } + + @Test + fun `wrapToString should return unformatted ToString`() { + assertEquals( + ToString.wrap(TextResource(1), null, null), + TextResource(1).wrapToString(), + ) + } + + @Test + fun `wrapToString should return identity for ToString`() { + val format = "format".asAString().format(1) + assertSame( + format, + format.wrapToString(), + ) + val toString = TextResource(1).wrapToString() + assertSame( + toString, + toString.wrapToString(), + ) + } + + @Test + fun `wrapToString should return NULL for null`() { + assertSame( + nullAsAString, + null.wrapToString(), + ) + assertSame( + nullAsAString, + null.asAString().wrapToString(), + ) + } + + @Test + fun `wrapToString should return new Wrapper`() { + assertEquals( + "format".asAString(), + StringBuilder("format").asAString().wrapToString(), + ) + } + + @Test + fun `wrapToString should return identity for Wrapper of String`() { + val wrapper = "format".asAString() + assertSame( + wrapper, + wrapper.wrapToString(), + ) + } + + @Test + fun `wrapToString should return identity for Provider`() { + Provider.values().forEach { + assertSame(it, it.wrapToString()) + } + } + @Test fun `appIdAString should be an identity`() { assertSame( diff --git a/astring/src/test/kotlin/xyz/tynn/astring/AppIdProviderTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/AppIdProviderTest.kt index 64e602b..0e4ef86 100644 --- a/astring/src/test/kotlin/xyz/tynn/astring/AppIdProviderTest.kt +++ b/astring/src/test/kotlin/xyz/tynn/astring/AppIdProviderTest.kt @@ -18,7 +18,7 @@ internal class AppIdProviderTest { assertEquals( "foo", - ContextValueProvider.AppIdProvider.invoke(context), + Provider.AppIdProvider.invoke(context), ) } @@ -26,7 +26,7 @@ internal class AppIdProviderTest { @Suppress("KotlinConstantConditions") fun `equals should be true for same type`() { assertTrue { - ContextValueProvider.AppIdProvider == ContextValueProvider.AppIdProvider + Provider.AppIdProvider == Provider.AppIdProvider } } @@ -34,16 +34,16 @@ internal class AppIdProviderTest { @Suppress("EqualsBetweenInconvertibleTypes", "KotlinConstantConditions") fun `equals should be false for non AppIdProvider`() { assertFalse { - ContextValueProvider.AppIdProvider.equals("foo") + Provider.AppIdProvider.equals("foo") } assertFalse { - ContextValueProvider.AppIdProvider == mockk() + Provider.AppIdProvider == mockk() } assertFalse { - ContextValueProvider.AppIdProvider == ContextValueProvider.AppVersionProvider + Provider.AppIdProvider == Provider.AppVersionProvider } assertFalse { - ContextValueProvider.AppIdProvider == mockk() + Provider.AppIdProvider == mockk() } } @@ -51,7 +51,7 @@ internal class AppIdProviderTest { fun `hashCode should return 0`() { assertNotEquals( 0, - ContextValueProvider.AppIdProvider.hashCode(), + Provider.AppIdProvider.hashCode(), ) } @@ -59,7 +59,7 @@ internal class AppIdProviderTest { fun `toString should return typed string`() { assertEquals( "AString(Context(appId))", - ContextValueProvider.AppIdProvider.toString(), + Provider.AppIdProvider.toString(), ) } } diff --git a/astring/src/test/kotlin/xyz/tynn/astring/AppVersionProviderTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/AppVersionProviderTest.kt index c724b18..abd4b84 100644 --- a/astring/src/test/kotlin/xyz/tynn/astring/AppVersionProviderTest.kt +++ b/astring/src/test/kotlin/xyz/tynn/astring/AppVersionProviderTest.kt @@ -23,7 +23,7 @@ internal class AppVersionProviderTest { assertEquals( "foo", - ContextValueProvider.AppVersionProvider.invoke(context), + Provider.AppVersionProvider.invoke(context), ) } @@ -31,7 +31,7 @@ internal class AppVersionProviderTest { @Suppress("KotlinConstantConditions") fun `equals should be true for same type`() { assertTrue { - ContextValueProvider.AppVersionProvider == ContextValueProvider.AppVersionProvider + Provider.AppVersionProvider == Provider.AppVersionProvider } } @@ -39,16 +39,16 @@ internal class AppVersionProviderTest { @Suppress("EqualsBetweenInconvertibleTypes", "KotlinConstantConditions") fun `equals should be false for non AppVersionProvider`() { assertFalse { - ContextValueProvider.AppVersionProvider.equals("foo") + Provider.AppVersionProvider.equals("foo") } assertFalse { - ContextValueProvider.AppVersionProvider == mockk() + Provider.AppVersionProvider == mockk() } assertFalse { - ContextValueProvider.AppVersionProvider == ContextValueProvider.AppIdProvider + Provider.AppVersionProvider == Provider.AppIdProvider } assertFalse { - ContextValueProvider.AppVersionProvider == mockk() + Provider.AppVersionProvider == mockk() } } @@ -56,7 +56,7 @@ internal class AppVersionProviderTest { fun `hashCode should not return 0`() { assertNotEquals( 0, - ContextValueProvider.AppVersionProvider.hashCode(), + Provider.AppVersionProvider.hashCode(), ) } @@ -64,7 +64,7 @@ internal class AppVersionProviderTest { fun `toString should return typed string`() { assertEquals( "AString(Context(appVersion))", - ContextValueProvider.AppVersionProvider.toString(), + Provider.AppVersionProvider.toString(), ) } } diff --git a/astring/src/test/kotlin/xyz/tynn/astring/NullValueProviderTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/NullTest.kt similarity index 53% rename from astring/src/test/kotlin/xyz/tynn/astring/NullValueProviderTest.kt rename to astring/src/test/kotlin/xyz/tynn/astring/NullTest.kt index a4dc4a7..9fb5583 100644 --- a/astring/src/test/kotlin/xyz/tynn/astring/NullValueProviderTest.kt +++ b/astring/src/test/kotlin/xyz/tynn/astring/NullTest.kt @@ -4,41 +4,51 @@ package xyz.tynn.astring import io.mockk.mockk -import kotlin.test.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue -internal class NullValueProviderTest { +internal class NullTest { @Test fun `invoke should return null`() { assertNull( - CharSequenceWrapper(null).invoke(mockk()), + Wrapper.NULL.invoke(mockk()), ) } + @Test + fun `identity should be true for same type`() { + assertTrue { + Wrapper.NULL === Wrapper.wrap(null) + } + } + @Test fun `equals should be true for same type`() { assertTrue { - CharSequenceWrapper(null) == CharSequenceWrapper(null) + Wrapper.NULL == Wrapper.NULL } } @Test - @Suppress("EqualsBetweenInconvertibleTypes") fun `equals should be false for non NullValueProvider`() { assertFalse { - CharSequenceWrapper(null).equals("foo") + Wrapper.NULL.equals("foo") } assertFalse { - CharSequenceWrapper(null) == mockk() + Wrapper.NULL == mockk() } assertFalse { - CharSequenceWrapper(null) == CharSequenceWrapper("") + Wrapper.NULL == Wrapper.wrap("") } assertFalse { - CharSequenceWrapper(null).equals(mockk()) + Wrapper.NULL.equals(mockk()) } assertFalse { - CharSequenceWrapper(null).equals(mockk()) + Wrapper.NULL.equals(mockk()) } } @@ -46,7 +56,7 @@ internal class NullValueProviderTest { fun `hashCode should return 0`() { assertEquals( 0, - CharSequenceWrapper(null).hashCode(), + Wrapper.NULL.hashCode(), ) } @@ -54,7 +64,7 @@ internal class NullValueProviderTest { fun `toString should return typed string`() { assertEquals( "AString(CharSequence(null))", - CharSequenceWrapper(null).toString(), + Wrapper.NULL.toString(), ) } } diff --git a/astring/src/test/kotlin/xyz/tynn/astring/QuantityStringResourceDelegateTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/QuantityStringResourceDelegateTest.kt deleted file mode 100644 index 32dcdf5..0000000 --- a/astring/src/test/kotlin/xyz/tynn/astring/QuantityStringResourceDelegateTest.kt +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2020 Christian Schmitz -// SPDX-License-Identifier: Apache-2.0 - -package xyz.tynn.astring - -import android.content.Context -import io.mockk.every -import io.mockk.mockk -import java.util.Locale -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -internal class QuantityStringResourceDelegateTest { - - @Test - fun `invoke should return string without format args`() { - val context = mockk { - every { resources.getQuantityText(1, 2) } returns "foo" - } - - assertEquals( - "foo", - ResourceDelegate.quantityString(1, 2, arrayOf()).invoke(context), - ) - } - - @Test - fun `invoke should return string with format args`() { - val context = mockk { - every { resources.getQuantityText(1, 2) } returns "foo%d%s" - every { resources.configuration } returns mockk { - @Suppress("DEPRECATION") - locale = Locale.getDefault() - } - } - - assertEquals( - "foo34", - ResourceDelegate.quantityString(1, 2, arrayOf(3, "4")).invoke(context), - ) - } - - @Test - fun `equals should be true for matching string resources and quantities`() { - assertTrue { - ResourceDelegate.quantityString(1, 2, null) == - ResourceDelegate.quantityString(1, 2, arrayOf()) - } - assertTrue { - ResourceDelegate.quantityString(1, 2, arrayOf()) == - ResourceDelegate.quantityString(1, 2, arrayOf()) - } - assertTrue { - ResourceDelegate.quantityString(1, 2, arrayOf(2, "3")) == - ResourceDelegate.quantityString(1, 2, arrayOf(2, "3")) - } - } - - @Test - fun `equals should be false for non matching string resources or quantities`() { - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf()) == - ResourceDelegate.quantityString(1, 1, arrayOf()) - } - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf(3, "4")) == - ResourceDelegate.quantityString(1, 1, arrayOf(3, "4")) - } - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf()) == - ResourceDelegate.quantityString(2, 2, arrayOf()) - } - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf(3, "4")) == - ResourceDelegate.quantityString(2, 2, arrayOf(3, "4")) - } - } - - @Test - fun `equals should be false for non matching format args`() { - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf(3, 4)) == - ResourceDelegate.quantityString(1, 2, arrayOf("3", "4")) - } - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf()) == - ResourceDelegate.quantityString(1, 2, arrayOf(3, "4")) - } - } - - @Test - fun `equals should be false for non QuantityStringResourceDelegate`() { - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf()).equals("foo") - } - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf()) == mockk() - } - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf()).equals(mockk()) - } - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf()).equals(mockk()) - } - assertFalse { - ResourceDelegate.quantityString(1, 2, arrayOf()).equals(mockk()) - } - } - - @Test - fun `hashCode should return delegate to string resource and quantity`() { - assertEquals( - 1202335992, - ResourceDelegate.quantityString(1, 2, null).hashCode(), - ) - assertEquals( - 1202335992, - ResourceDelegate.quantityString(1, 2, arrayOf()).hashCode(), - ) - assertEquals( - 1202337098, - ResourceDelegate.quantityString(1, 2, arrayOf(3, "4")).hashCode(), - ) - } - - @Test - fun `toString should return typed string`() { - assertEquals( - "AString(QuantityStringResource(1,2))", - ResourceDelegate.quantityString(1, 2, arrayOf()).toString(), - ) - assertEquals( - "AString(QuantityStringResource(1,2,3,4))", - ResourceDelegate.quantityString(1, 2, arrayOf(3, "4")).toString(), - ) - } -} diff --git a/astring/src/test/kotlin/xyz/tynn/astring/QuantityStringResourceTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/QuantityStringResourceTest.kt new file mode 100644 index 0000000..9b41e22 --- /dev/null +++ b/astring/src/test/kotlin/xyz/tynn/astring/QuantityStringResourceTest.kt @@ -0,0 +1,142 @@ +// Copyright 2020 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring + +import android.content.Context +import io.mockk.every +import io.mockk.mockk +import java.util.Locale +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +internal class QuantityStringResourceTest { + + @Test + fun `invoke should return string without format args`() { + val context = mockk { + every { resources.getQuantityText(1, 2) } returns "foo" + } + + assertEquals( + "foo", + Resource.wrap(1, 2, arrayOf()).invoke(context), + ) + } + + @Test + fun `invoke should return string with format args`() { + val context = mockk { + every { resources.getQuantityText(1, 2) } returns "foo%d%s" + every { resources.configuration } returns mockk { + @Suppress("DEPRECATION") + locale = Locale.getDefault() + } + } + + assertEquals( + "foo34", + Resource.wrap(1, 2, arrayOf(3, "4")).invoke(context), + ) + } + + @Test + fun `equals should be true for matching string resources and quantities`() { + assertTrue { + Resource.wrap(1, 2, null) == + Resource.wrap(1, 2, arrayOf()) + } + assertTrue { + Resource.wrap(1, 2, arrayOf()) == + Resource.wrap(1, 2, arrayOf()) + } + assertTrue { + Resource.wrap(1, 2, arrayOf(2, "3")) == + Resource.wrap(1, 2, arrayOf(2, "3")) + } + } + + @Test + fun `equals should be false for non matching string resources or quantities`() { + assertFalse { + Resource.wrap(1, 2, arrayOf()) == + Resource.wrap(1, 1, arrayOf()) + } + assertFalse { + Resource.wrap(1, 2, arrayOf(3, "4")) == + Resource.wrap(1, 1, arrayOf(3, "4")) + } + assertFalse { + Resource.wrap(1, 2, arrayOf()) == + Resource.wrap(2, 2, arrayOf()) + } + assertFalse { + Resource.wrap(1, 2, arrayOf(3, "4")) == + Resource.wrap(2, 2, arrayOf(3, "4")) + } + } + + @Test + fun `equals should be false for non matching format args`() { + assertFalse { + Resource.wrap(1, 2, arrayOf(3, 4)) == + Resource.wrap(1, 2, arrayOf("3", "4")) + } + assertFalse { + Resource.wrap(1, 2, arrayOf()) == + Resource.wrap(1, 2, arrayOf(3, "4")) + } + } + + @Test + fun `equals should be false for non QuantityStringResourceDelegate`() { + assertFalse { + Resource.wrap(1, 2, arrayOf()).equals("foo") + } + assertFalse { + Resource.wrap(1, 2, arrayOf()) == mockk() + } + assertFalse { + Resource.wrap(1, 2, arrayOf()) + .equals(mockk()) + } + assertFalse { + Resource.wrap(1, 2, arrayOf()) + .equals(mockk()) + } + assertFalse { + Resource.wrap(1, 2, arrayOf()) + .equals(mockk()) + } + } + + @Test + fun `hashCode should return delegate to string resource and quantity`() { + assertEquals( + 985025, + Resource.wrap(1, 2, null).hashCode(), + ) + assertEquals( + 985025, + Resource.wrap(1, 2, arrayOf()).hashCode(), + ) + assertEquals( + 986131, + Resource.wrap(1, 2, arrayOf(3, "4")).hashCode(), + ) + } + + @Test + fun `toString should return typed string`() { + assertEquals( + "AString(String(AString(QuantityTextResource(1,2))))", + Resource.wrap(1, 2, arrayOf()).toString(), + ) + assertEquals( + "AString(Format(AString(QuantityTextResource(1,2)),3,4))", + Resource.wrap(1, 2, arrayOf(3, "4")).toString(), + ) + } +} diff --git a/astring/src/test/kotlin/xyz/tynn/astring/QuantityTextResourceDelegateTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/QuantityTextResourceTest.kt similarity index 56% rename from astring/src/test/kotlin/xyz/tynn/astring/QuantityTextResourceDelegateTest.kt rename to astring/src/test/kotlin/xyz/tynn/astring/QuantityTextResourceTest.kt index 96ef2a9..69d5dca 100644 --- a/astring/src/test/kotlin/xyz/tynn/astring/QuantityTextResourceDelegateTest.kt +++ b/astring/src/test/kotlin/xyz/tynn/astring/QuantityTextResourceTest.kt @@ -11,7 +11,7 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -internal class QuantityTextResourceDelegateTest { +internal class QuantityTextResourceTest { @Test fun `invoke should return text`() { @@ -21,58 +21,58 @@ internal class QuantityTextResourceDelegateTest { assertEquals( "foo", - ResourceDelegate.quantityText(1, 2).invoke(context), + Resource.wrap(1, 2).invoke(context), ) } @Test fun `equals should be true for matching text resources and quantities`() { assertTrue { - ResourceDelegate.quantityText(1, 2) == - ResourceDelegate.quantityText(1, 2) + Resource.wrap(1, 2) == + Resource.wrap(1, 2) } } @Test fun `equals should be false for non matching text resources or quantities`() { assertFalse { - ResourceDelegate.quantityText(1, 2) == - ResourceDelegate.quantityText(1, 1) + Resource.wrap(1, 2) == + Resource.wrap(1, 1) } assertFalse { - ResourceDelegate.quantityText(1, 2) == - ResourceDelegate.quantityText(2, 2) + Resource.wrap(1, 2) == + Resource.wrap(2, 2) } } @Test fun `equals should be false for non QuantityTextResourceDelegate`() { assertFalse { - ResourceDelegate.quantityText(1, 2).equals("foo") + Resource.wrap(1, 2).equals("foo") } assertFalse { - ResourceDelegate.quantityText(1, 2) == mockk() + Resource.wrap(1, 2) == mockk() } assertFalse { - ResourceDelegate.quantityText(1, 2).equals(mockk()) + Resource.wrap(1, 2).equals(mockk()) } assertFalse { - ResourceDelegate.quantityText(1, 2).equals(mockk()) + Resource.wrap(1, 2).equals(mockk()) } assertFalse { - ResourceDelegate.quantityText(1, 2).equals(mockk()) + Resource.wrap(1, 2).equals(mockk()) } } @Test fun `hashCode should return delegate to text resource and quantity`() { assertEquals( - 1202157246, - ResourceDelegate.quantityText(1, 2).hashCode(), + 994, + Resource.wrap(1, 2).hashCode(), ) assertEquals( - 1202158207, - ResourceDelegate.quantityText(2, 2).hashCode(), + 1025, + Resource.wrap(2, 2).hashCode(), ) } @@ -80,7 +80,7 @@ internal class QuantityTextResourceDelegateTest { fun `toString should return typed string`() { assertEquals( "AString(QuantityTextResource(1,2))", - ResourceDelegate.quantityText(1, 2).toString(), + Resource.wrap(1, 2).toString(), ) } } diff --git a/astring/src/test/kotlin/xyz/tynn/astring/StringResourceDelegateTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/StringResourceDelegateTest.kt deleted file mode 100644 index f4bcee6..0000000 --- a/astring/src/test/kotlin/xyz/tynn/astring/StringResourceDelegateTest.kt +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2020 Christian Schmitz -// SPDX-License-Identifier: Apache-2.0 - -package xyz.tynn.astring - -import android.content.Context -import io.mockk.every -import io.mockk.mockk -import java.util.Locale -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -internal class StringResourceDelegateTest { - - @Test - fun `invoke should return string without format args`() { - val context = mockk { - every { resources.getText(1) } returns StringBuilder("foo") - } - - assertEquals( - "foo", - ResourceDelegate.string(1, arrayOf()).invoke(context), - ) - } - - @Test - fun `invoke should return string with format args`() { - val context = mockk { - every { resources.getText(1) } returns "foo%d%s" - every { resources.configuration } returns mockk { - @Suppress("DEPRECATION") - locale = Locale.getDefault() - } - } - - assertEquals( - "foo23", - ResourceDelegate.string(1, arrayOf(2, "3")).invoke(context), - ) - } - - @Test - fun `equals should be true for matching string resources`() { - assertTrue { - ResourceDelegate.string(1, null) == - ResourceDelegate.string(1, arrayOf()) - } - assertTrue { - ResourceDelegate.string(1, arrayOf()) == - ResourceDelegate.string(1, arrayOf()) - } - assertTrue { - ResourceDelegate.string(1, arrayOf(2, "3")) == - ResourceDelegate.string(1, arrayOf(2, "3")) - } - } - - @Test - fun `equals should be false for non matching string resources`() { - assertFalse { - ResourceDelegate.string(1, arrayOf()) == - ResourceDelegate.string(2, arrayOf()) - } - assertFalse { - ResourceDelegate.string(1, arrayOf(2, "3")) == - ResourceDelegate.string(2, arrayOf(2, "3")) - } - } - - @Test - fun `equals should be false for non matching format args`() { - assertFalse { - ResourceDelegate.string(1, arrayOf(2, 3)) == - ResourceDelegate.string(1, arrayOf("2", "3")) - } - assertFalse { - ResourceDelegate.string(1, arrayOf()) == - ResourceDelegate.string(1, arrayOf(2, "3")) - } - } - - @Test - fun `equals should be false for non StringResourceDelegate`() { - assertFalse { - ResourceDelegate.string(1, arrayOf()).equals("foo") - } - assertFalse { - ResourceDelegate.string(1, arrayOf()) == mockk() - } - assertFalse { - ResourceDelegate.string(1, arrayOf()).equals(mockk()) - } - assertFalse { - ResourceDelegate.string(1, arrayOf()).equals(mockk()) - } - assertFalse { - ResourceDelegate.string(1, arrayOf()).equals(mockk()) - } - } - - @Test - fun `hashCode should return delegate to string resource`() { - assertEquals( - -939606592, - ResourceDelegate.string(1, null).hashCode(), - ) - assertEquals( - -939606592, - ResourceDelegate.string(1, arrayOf()).hashCode(), - ) - assertEquals( - -939605518, - ResourceDelegate.string(1, arrayOf(2, "3")).hashCode(), - ) - } - - @Test - fun `toString should return typed string`() { - assertEquals( - "AString(StringResource(1))", - ResourceDelegate.string(1, arrayOf()).toString(), - ) - assertEquals( - "AString(StringResource(1,2,3))", - ResourceDelegate.string(1, arrayOf(2, "3")).toString(), - ) - } -} diff --git a/astring/src/test/kotlin/xyz/tynn/astring/StringResourceTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/StringResourceTest.kt new file mode 100644 index 0000000..540278b --- /dev/null +++ b/astring/src/test/kotlin/xyz/tynn/astring/StringResourceTest.kt @@ -0,0 +1,134 @@ +// Copyright 2020 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring + +import android.content.Context +import io.mockk.every +import io.mockk.mockk +import java.util.Locale +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +internal class StringResourceTest { + + @Test + fun `invoke should return string without format args`() { + val context = mockk { + every { getText(1) } returns StringBuilder("foo") + } + + assertEquals( + "foo", + Resource.wrap(1, null, arrayOf()).invoke(context), + ) + } + + @Test + fun `invoke should return string with format args`() { + val context = mockk { + every { getText(1) } returns "foo%d%s" + every { resources.configuration } returns mockk { + @Suppress("DEPRECATION") + locale = Locale.getDefault() + } + } + + assertEquals( + "foo23", + Resource.wrap(1, null, arrayOf(2, "3")).invoke(context), + ) + } + + @Test + fun `equals should be true for matching string resources`() { + assertTrue { + Resource.wrap(1, null, null) == + Resource.wrap(1, null, arrayOf()) + } + assertTrue { + Resource.wrap(1, null, arrayOf()) == + Resource.wrap(1, null, arrayOf()) + } + assertTrue { + Resource.wrap(1, null, arrayOf(2, "3")) == + Resource.wrap(1, null, arrayOf(2, "3")) + } + } + + @Test + fun `equals should be false for non matching string resources`() { + assertFalse { + Resource.wrap(1, null, arrayOf()) == + Resource.wrap(2, null, arrayOf()) + } + assertFalse { + Resource.wrap(1, null, arrayOf(2, "3")) == + Resource.wrap(2, null, arrayOf(2, "3")) + } + } + + @Test + fun `equals should be false for non matching format args`() { + assertFalse { + Resource.wrap(1, null, arrayOf(2, 3)) == + Resource.wrap(1, null, arrayOf("2", "3")) + } + assertFalse { + Resource.wrap(1, null, arrayOf()) == + Resource.wrap(1, null, arrayOf(2, "3")) + } + } + + @Test + fun `equals should be false for non StringResourceDelegate`() { + assertFalse { + Resource.wrap(1, null, arrayOf()).equals("foo") + } + assertFalse { + Resource.wrap(1, null, arrayOf()) == mockk() + } + assertFalse { + Resource.wrap(1, null, arrayOf()) + .equals(mockk()) + } + assertFalse { + Resource.wrap(1, null, arrayOf()) + .equals(mockk()) + } + assertFalse { + Resource.wrap(1, null, arrayOf()) + .equals(mockk()) + } + } + + @Test + fun `hashCode should return delegate to string resource`() { + assertEquals( + 983103, + Resource.wrap(1, null, null).hashCode(), + ) + assertEquals( + 983103, + Resource.wrap(1, null, arrayOf()).hashCode(), + ) + assertEquals( + 984177, + Resource.wrap(1, null, arrayOf(2, "3")).hashCode(), + ) + } + + @Test + fun `toString should return typed string`() { + assertEquals( + "AString(String(AString(TextResource(1))))", + Resource.wrap(1, null, arrayOf()).toString(), + ) + assertEquals( + "AString(Format(AString(TextResource(1)),2,3))", + Resource.wrap(1, null, arrayOf(2, "3")).toString(), + ) + } +} diff --git a/astring/src/test/kotlin/xyz/tynn/astring/TextResourceDelegateTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/TextResourceTest.kt similarity index 60% rename from astring/src/test/kotlin/xyz/tynn/astring/TextResourceDelegateTest.kt rename to astring/src/test/kotlin/xyz/tynn/astring/TextResourceTest.kt index 2b0c40d..8ef59b3 100644 --- a/astring/src/test/kotlin/xyz/tynn/astring/TextResourceDelegateTest.kt +++ b/astring/src/test/kotlin/xyz/tynn/astring/TextResourceTest.kt @@ -11,62 +11,64 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -internal class TextResourceDelegateTest { +internal class TextResourceTest { @Test fun `invoke should return text`() { val context = mockk { - every { resources.getText(1) } returns "foo" + every { getText(1) } returns "foo" } assertEquals( "foo", - ResourceDelegate.text(1).invoke(context), + Resource.wrap(1, null).invoke(context), ) } @Test fun `equals should be true for matching text resources`() { assertTrue { - ResourceDelegate.text(1) == ResourceDelegate.text(1) + Resource.wrap(1, null) == + Resource.wrap(1, null) } } @Test fun `equals should be false for non matching text resources`() { assertFalse { - ResourceDelegate.text(1) == ResourceDelegate.text(2) + Resource.wrap(1, null) == + Resource.wrap(2, null) } } @Test fun `equals should be false for non TextResourceDelegate`() { assertFalse { - ResourceDelegate.text(1).equals("foo") + Resource.wrap(1, null).equals("foo") } assertFalse { - ResourceDelegate.text(1) == mockk() + Resource.wrap(1, null) == mockk() } assertFalse { - ResourceDelegate.text(1).equals(mockk()) + Resource.wrap(1, null).equals(mockk()) } assertFalse { - ResourceDelegate.text(1).equals(mockk()) + Resource.wrap(1, null).equals(mockk()) } assertFalse { - ResourceDelegate.text(1).equals(mockk()) + Resource.wrap(1, null).equals(mockk()) } } @Test fun `hashCode should return delegate to text resource`() { assertEquals( - -939785338, - ResourceDelegate.text(1).hashCode(), + 992, + Resource.wrap(1, null).hashCode(), ) assertEquals( - -939784377, - ResourceDelegate.text(2).hashCode(), + 1023, + Resource.wrap(2, null).hashCode(), ) } @@ -74,7 +76,7 @@ internal class TextResourceDelegateTest { fun `toString should return typed string`() { assertEquals( "AString(TextResource(1))", - ResourceDelegate.text(1).toString(), + Resource.wrap(1, null).toString(), ) } } diff --git a/astring/src/test/kotlin/xyz/tynn/astring/ToStringTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/ToStringTest.kt new file mode 100644 index 0000000..acdada2 --- /dev/null +++ b/astring/src/test/kotlin/xyz/tynn/astring/ToStringTest.kt @@ -0,0 +1,188 @@ +// Copyright 2023 Christian Schmitz +// SPDX-License-Identifier: Apache-2.0 + +package xyz.tynn.astring + +import android.content.Context +import android.os.Parcel +import io.mockk.every +import io.mockk.mockk +import java.util.Locale +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +internal class ToStringTest { + + private val locale = Locale.CANADA + private val format = Format() + + @Test + fun `invoke should return string without locale`() { + val context = mockk { + every { resources.configuration } returns mockk { + @Suppress("DEPRECATION") + locale = Locale.FRENCH + } + } + + assertEquals( + "23", + ToString.wrap(format, null, arrayOf(2, "3")).invoke(context), + ) + } + + @Test + fun `invoke should return string with locale`() { + assertEquals( + "23", + ToString.wrap(format, locale, arrayOf(2, "3")) + .invoke(mockk()), + ) + } + + @Test + fun `equals should be true for matching formats and args`() { + assertTrue { + ToString.wrap(format, null, null) == + ToString.wrap(format, locale, arrayOf()) + } + assertTrue { + ToString.wrap(format, Locale.GERMAN, arrayOf()) == + ToString.wrap(format, locale, arrayOf()) + } + assertTrue { + ToString.wrap(format, locale, null) == + ToString.wrap(format, locale, arrayOf()) + } + assertTrue { + ToString.wrap(format, null, arrayOf()) == + ToString.wrap(format, null, arrayOf()) + } + assertTrue { + ToString.wrap(format, locale, arrayOf()) == + ToString.wrap(format, locale, arrayOf()) + } + assertTrue { + ToString.wrap(format, null, arrayOf(2, "3")) == + ToString.wrap(format, null, arrayOf(2, "3")) + } + assertTrue { + ToString.wrap(format, locale, arrayOf(2, "3")) == + ToString.wrap(format, locale, arrayOf(2, "3")) + } + } + + @Test + fun `equals should be false for non matching locales`() { + assertFalse { + ToString.wrap(format, Locale.GERMAN, arrayOf(2, "3")) == + ToString.wrap(format, locale, arrayOf(2, "3")) + } + } + + @Test + fun `equals should be false for non matching formats`() { + assertFalse { + ToString.wrap(format, null, arrayOf()) == + ToString.wrap(TextResource(1), null, arrayOf()) + } + assertFalse { + ToString.wrap(format, null, arrayOf(2, "3")) == + ToString.wrap(TextResource(1), null, arrayOf(2, "3")) + } + } + + @Test + fun `equals should be false for non matching format args`() { + assertFalse { + ToString.wrap(format, null, arrayOf(2, 3)) == + ToString.wrap(format, null, arrayOf("2", "3")) + } + assertFalse { + ToString.wrap(format, locale, arrayOf(2, 3)) == + ToString.wrap(format, locale, arrayOf("2", "3")) + } + assertFalse { + ToString.wrap(format, null, arrayOf()) == + ToString.wrap(format, null, arrayOf(2, "3")) + } + assertFalse { + ToString.wrap(format, locale, arrayOf()) == + ToString.wrap(format, locale, arrayOf(2, "3")) + } + } + + @Test + fun `equals should be false for non StringFormat`() { + assertFalse { + ToString.wrap(format, null, arrayOf()).equals("foo") + } + assertFalse { + ToString.wrap(format, null, arrayOf()) == mockk() + } + assertFalse { + ToString.wrap(format, null, arrayOf()).equals(mockk()) + } + assertFalse { + ToString.wrap(format, null, arrayOf()).equals(mockk()) + } + assertFalse { + ToString.wrap(format, null, arrayOf()).equals(mockk()) + } + } + + @Test + fun `hashCode should return delegate to locale and format and args`() { + assertEquals( + 40362, + ToString.wrap(format, null, null).hashCode(), + ) + assertEquals( + 40362, + ToString.wrap(format, locale, null).hashCode(), + ) + assertEquals( + 40362, + ToString.wrap(format, null, arrayOf()).hashCode(), + ) + assertEquals( + 40362, + ToString.wrap(format, locale, arrayOf()).hashCode(), + ) + assertEquals( + 41436, + ToString.wrap(format, null, arrayOf(2, "3")).hashCode(), + ) + assertEquals( + -1299735837, + ToString.wrap(format, locale, arrayOf(2, "3")) + .hashCode(), + ) + } + + @Test + fun `toString should return typed string`() { + assertEquals( + "AString(String($format))", + ToString.wrap(format, null, arrayOf()).toString(), + ) + assertEquals( + "AString(Format($format,2,3))", + ToString.wrap(format, null, arrayOf(2, "3")).toString(), + ) + assertEquals( + "AString(Format($locale,$format,2,3))", + ToString.wrap(format, locale, arrayOf(2, "3")) + .toString(), + ) + } + + private class Format : AString { + override fun invoke(context: Context) = "%d%s" + override fun writeToParcel(dest: Parcel, flags: Int) = Unit + override fun equals(other: Any?) = other is Format + override fun hashCode() = 11 + } +} diff --git a/astring/src/test/kotlin/xyz/tynn/astring/CharSequenceWrapperTest.kt b/astring/src/test/kotlin/xyz/tynn/astring/WrapperTest.kt similarity index 61% rename from astring/src/test/kotlin/xyz/tynn/astring/CharSequenceWrapperTest.kt rename to astring/src/test/kotlin/xyz/tynn/astring/WrapperTest.kt index 80e7747..fa8e894 100644 --- a/astring/src/test/kotlin/xyz/tynn/astring/CharSequenceWrapperTest.kt +++ b/astring/src/test/kotlin/xyz/tynn/astring/WrapperTest.kt @@ -9,46 +9,46 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -internal class CharSequenceWrapperTest { +internal class WrapperTest { @Test fun `invoke should return the string`() { assertEquals( "foo", - CharSequenceWrapper("foo").invoke(null), + Wrapper.wrap("foo").invoke(null), ) } @Test fun `equals should be true for matching strings`() { assertTrue { - CharSequenceWrapper("foo") == CharSequenceWrapper("foo") + Wrapper.wrap("foo") == Wrapper.wrap("foo") } } @Test fun `equals should be false for non matching strings`() { assertFalse { - CharSequenceWrapper("foo") == CharSequenceWrapper("bar") + Wrapper.wrap("foo") == Wrapper.wrap("bar") } } @Test fun `equals should be false for non CharSequenceWrapper`() { assertFalse { - CharSequenceWrapper("foo").equals("foo") + Wrapper.wrap("foo").equals("foo") } assertFalse { - CharSequenceWrapper("foo") == mockk() + Wrapper.wrap("foo") == mockk() } assertFalse { - CharSequenceWrapper("foo") == mockk() + Wrapper.wrap("foo") == mockk() } assertFalse { - CharSequenceWrapper("foo").equals(mockk()) + Wrapper.wrap("foo").equals(mockk()) } assertFalse { - CharSequenceWrapper("foo").equals(mockk()) + Wrapper.wrap("foo").equals(mockk()) } } @@ -56,11 +56,11 @@ internal class CharSequenceWrapperTest { fun `hashCode should return delegate to string`() { assertEquals( "foo".hashCode(), - CharSequenceWrapper("foo").hashCode(), + Wrapper.wrap("foo").hashCode(), ) assertEquals( "bar".hashCode(), - CharSequenceWrapper("bar").hashCode(), + Wrapper.wrap("bar").hashCode(), ) } @@ -68,7 +68,7 @@ internal class CharSequenceWrapperTest { fun `toString should return typed string`() { assertEquals( "AString(CharSequence(foo))", - CharSequenceWrapper("foo").toString(), + Wrapper.wrap("foo").toString(), ) } } diff --git a/build.gradle b/build.gradle index 1c565ea..4a330cc 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { alias libs.plugins.android apply false alias libs.plugins.kotlin apply false alias libs.plugins.kotlin.kapt apply false - alias libs.plugins.project.convention + alias libs.plugins.conventions alias libs.plugins.publish } diff --git a/compose/build.gradle b/compose/build.gradle index f0c75fc..cbf301b 100644 --- a/compose/build.gradle +++ b/compose/build.gradle @@ -18,12 +18,12 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } -// kotlinOptions { -// freeCompilerArgs += [ -// '-P', 'plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=' + -// libs.versions.kotlin.get() -// ] -// } + kotlinOptions { + freeCompilerArgs += [ + '-P', 'plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=' + + libs.versions.kotlin.get() + ] + } testOptions { unitTests { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fe127c3..fd2f304 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,13 @@ [versions] android = "8.1.4" compose-compiler = "1.5.4" -kotlin = "1.9.20" +kotlin = "1.9.21" [plugins] android = { id = "com.android.library", version.ref = "android" } +conventions = "xyz.tynn.convention.project:0.0.4" kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } -project-convention = "xyz.tynn.convention.project:0.0.4" publish = "io.github.gradle-nexus.publish-plugin:1.3.0" [libraries]