Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Password comp #1316

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
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
49 changes: 27 additions & 22 deletions .github/workflows/exporter-validate-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,38 @@ jobs:

# Loop through each changed file
for file in $changed_files; do
# Fetch the base and head versions of the file
base_file=$(git show ${{ github.base_ref }}:$file)
head_file=$(git show ${{ github.head_ref }}:$file)
# Check if the file exists in both branches
if git cat-file -e origin/${{ github.base_ref }}:$file 2>/dev/null && git cat-file -e origin/${{ github.head_ref }}:$file 2>/dev/null; then
# Fetch the base and head versions of the file
base_file=$(git show origin/${{ github.base_ref }}:$file)
head_file=$(git show origin/${{ github.head_ref }}:$file)

# Compare the JSON keys
base_keys=$(echo "$base_file" | jq -r 'paths | map(tostring) | join(".")' | sed 's/\./\\./g')
head_keys=$(echo "$head_file" | jq -r 'paths | map(tostring) | join(".")' | sed 's/\./\\./g')
# Compare the JSON keys
base_keys=$(echo "$base_file" | jq -r 'paths | map(tostring) | join(".")' | sed 's/\./\\./g')
head_keys=$(echo "$head_file" | jq -r 'paths | map(tostring) | join(".")' | sed 's/\./\\./g')

# Check for removed keys
removed_keys=$(comm -23 <(echo "$base_keys" | sort) <(echo "$head_keys" | sort))
# Check for removed keys
removed_keys=$(comm -23 <(echo "$base_keys" | sort) <(echo "$head_keys" | sort))

if [ -n "$removed_keys" ]; then
echo "Backward incompatibility change detected in $file. The following keys were removed:"
echo "$removed_keys"
exit 1
fi

# Check for changed values
for key in $base_keys; do
base_value=$(echo "$base_file" | jq -r --arg key "$key" '.[$key]')
head_value=$(echo "$head_file" | jq -r --arg key "$key" '.[$key]')

if [ "$base_value" != "$head_value" ]; then
echo "Backward incompatibility change detected in $file. The value of key '$key' was changed from '$base_value' to '$head_value'."
if [ -n "$removed_keys" ]; then
echo "Backward incompatibility change detected in $file. The following keys were removed:"
echo "$removed_keys"
exit 1
fi
done

# Check for changed values
for key in $base_keys; do
base_value=$(echo "$base_file" | jq -r ".$key")
head_value=$(echo "$head_file" | jq -r ".$key")

if [ "$base_value" != "$head_value" ]; then
echo "Backward incompatibility change detected in $file. The value of key '$key' was changed from '$base_value' to '$head_value'."
exit 1
fi
done
else
echo "Skipping file $file as it exists in one branch but not the other."
fi
done

echo "All exporter JSON files have only additions. No backward incompatibility changes detected."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,5 @@ private FormConstants() {

/* The resource type for the pre-selected the linked panel */
public final static String RT_FD_FORM_REVIEW_DATASOURCE_V1 = RT_FD_FORM_PREFIX + "review/v1/datasource";
public static final String RT_FD_FORM_PASSWORD_V1 = RT_FD_FORM_PREFIX + "password/v1/password";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2024 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

package com.adobe.cq.forms.core.components.internal.models.v1.form;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.forms.core.components.internal.form.FormConstants;
import com.adobe.cq.forms.core.components.models.form.FieldType;
import com.adobe.cq.forms.core.components.models.form.Password;
import com.adobe.cq.forms.core.components.util.AbstractFieldImpl;
import com.adobe.cq.forms.core.components.util.ComponentUtils;

@Model(
adaptables = { SlingHttpServletRequest.class, Resource.class },
adapters = { Password.class, ComponentExporter.class },
resourceType = { FormConstants.RT_FD_FORM_PASSWORD_V1 })
@Exporter(
name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class PasswordImpl extends AbstractFieldImpl implements Password {

private Object exclusiveMinimumVaue;
private Object exclusiveMaximumValue;

@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
@Nullable
private String pattern;

@Override
public String getFieldType() {
return getFieldType(FieldType.PASSWORD);
}

@Override
public Integer getMinLength() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we create an abstract method and re-use this, looks like it is copy / pasted from TextInput and NumberInput

Copy link
Contributor Author

@devgurjar devgurjar Nov 14, 2024

Choose a reason for hiding this comment

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

If I create an abstract method in AbstractFieldImpl, it is used by multiple components like switch, and getMinLength is not used there.

Copy link
Collaborator

@rismehta rismehta Nov 27, 2024

Choose a reason for hiding this comment

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

Since password would only extend text constraints, let the code be copy / pasted, since the number constraint implementation should be removed

return minLength;
}

@Override
public Integer getMaxLength() {
return maxLength;
}

@Override
public String getFormat() {
return displayFormat;
}

@Override
public String getPattern() {
return pattern;
}

@PostConstruct
private void initTextInput() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be named as initPassword and not initTextInput

exclusiveMaximumValue = ComponentUtils.getExclusiveValue(exclusiveMaximum, maximum, null);
exclusiveMinimumVaue = ComponentUtils.getExclusiveValue(exclusiveMinimum, minimum, null);
// in json either, exclusiveMaximum or maximum should be present
if (exclusiveMaximumValue != null) {
maximum = null;
}
if (exclusiveMinimumVaue != null) {
minimum = null;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2024 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

package com.adobe.cq.forms.core.components.models.form;

import org.osgi.annotation.versioning.ConsumerType;

/**
* Interface for {@code Password} Sling Model used for the {@code /apps/core/fd/components/form/password/v1/password} component.
*
* @since com.adobe.cq.forms.core.components.models.form 5.9.6
*/
@ConsumerType
public interface Password extends Field, StringConstraint {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please change the version and bump package-info file


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2024 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

package com.adobe.cq.forms.core.components.internal.models.v1.form;

import com.adobe.cq.forms.core.components.internal.form.FormConstants;
import com.adobe.cq.forms.core.components.models.form.TextInput;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import com.adobe.cq.forms.core.Utils;
import com.adobe.cq.forms.core.components.datalayer.FormComponentData;
import com.adobe.cq.forms.core.components.models.form.FieldType;
import com.adobe.cq.forms.core.components.models.form.Password;
import com.adobe.cq.forms.core.context.FormsCoreComponentTestContext;
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
import org.mockito.Mockito;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

@ExtendWith(AemContextExtension.class)
public class PasswordImplTest {

private static final String BASE = "/form/password";
private static final String CONTENT_ROOT = "/content";
private static final String PATH_PASSWORD_DATALAYER = CONTENT_ROOT + "/password-datalayer";
private static final String PATH_PASSWORD_CUSTOMIZED = CONTENT_ROOT + "/password-customized";

private static final String PATH_PASSWORD = CONTENT_ROOT + "/password";

private static final String PATH_PASSWORD_PATTERN = CONTENT_ROOT + "/password-pattern";

private final AemContext context = FormsCoreComponentTestContext.newAemContext();

@BeforeEach
void setUp() {
context.load().json(BASE + FormsCoreComponentTestContext.TEST_CONTENT_JSON, CONTENT_ROOT);
}

@Test
void testExportedType() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(FormConstants.RT_FD_FORM_PASSWORD_V1, password.getExportedType());
TextInput textInputMock = Mockito.mock(TextInput.class);
Mockito.when(textInputMock.getExportedType()).thenCallRealMethod();
assertEquals("", textInputMock.getExportedType());
}

@Test
void testJSONExport() throws Exception {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_PATTERN, Password.class, context);
Utils.testJSONExport(password, Utils.getTestExporterJSONPath(BASE, PATH_PASSWORD));
}

@Test
void testFieldType() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(FieldType.PASSWORD.getValue(), password.getFieldType());
}

@Test
void testGetLabel() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals("pwd", password.getLabel().getValue());
}

@Test
void testPlaceholder() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals("Enter valid password", password.getPlaceHolder());
}

@Test
void testGetName() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals("password", password.getName());

}

@Test
void testDorProperties() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(true, password.getDorProperties().get("dorExclusion"));
assertEquals("4", password.getDorProperties().get("dorColspan"));
assertEquals("Text1", password.getDorProperties().get("dorBindRef"));

}

@Test
void testGetDescription() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals("password field", password.getDescription());
}

@Test
void testGetRequired() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(true, password.isRequired());
}

@Test
void testGetValidationPattern() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals("/^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/", password.getPattern());

}

@Test
void testIsEnabled() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(true, password.isEnabled());
}

@Test
void testIsEnabledForCustomized() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(true, password.isEnabled());
}

@Test
void testIsReadOnly() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(false, password.isReadOnly());
}

@Test
void testIsReadOnlyForCustomized() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(false, password.isReadOnly());
}

@Test
void testMinLength() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(5, password.getMinLength().intValue());
}

@Test
void testMaxLength() {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals(10, password.getMaxLength().intValue());
}

@Test
void testGetDisplayFormat() throws Exception {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_CUSTOMIZED, Password.class, context);
assertEquals("password", password.getFormat());
}

@Test
void testGetPattern() throws Exception {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_PATTERN, Password.class, context);
assertEquals("^(?=.*\\d.*\\d)[A-Za-z\\d!@]+$", password.getPattern());
}

@Test
void testDataLayerProperties() throws IllegalAccessException {
Password password = Utils.getComponentUnderTest(PATH_PASSWORD_DATALAYER, Password.class, context);
FieldUtils.writeField(password, "dataLayerEnabled", true, true);
FormComponentData dataObject = (FormComponentData) password.getData();
assert (dataObject != null);
assert (dataObject.getId()).equals("password-1c7bc238a6");
assert (dataObject.getType()).equals("core/fd/components/form/password/v1/password");
assert (dataObject.getTitle()).equals("Full Name");
assert (dataObject.getFieldType()).equals("password");
assert (dataObject.getDescription()).equals("Enter Full Name");
}
}
Loading
Loading