Not sure if I totally understood your question but you can take a look at the Solidity Documentation Contract ABI Specification: https://docs.soliditylang.org/en/latest/abi-spec.html#examples
To see how to encode the parameters on your own. It is basically a big concatenated strings set of function name hash and function arguments.
You can also take a look at this lib which performs "custom contract calls" (so not relying on any ABI): https://github.com/ERC725Alliance/erc725.js/blob/main/src/lib/utils.ts
Manual way
import Web3 from 'web3';
const web3 = new Web3();
const methodId = web3.utils.keccak256('testCall(string)').substr(0, 10); //0xc7cee1b7
// In this function call, the first and only parameter uses a dynamic type: string
// https://docs.soliditylang.org/en/latest/abi-spec.html#use-of-dynamic-types
// we use the offset in bytes to the start of their data area, measured from the start of the value encoding
// offset to start of data part of second parameter, 1*32 bytes, exactly the size of the head part
const stringArgument = web3.utils.padLeft(web3.utils.toHex(1*32), 64).replace('0x', '');
// 0000000000000000000000000000000000000000000000000000000000000020
const stringValue = 'TODO' // This one is a bit tricky, the docs explain it well: https://docs.soliditylang.org/en/latest/abi-spec.html#use-of-dynamic-types
const call = methodId + stringArgument + stringValue;
ABI way
You can always construct your own ABI dict, then web3 will handle the above conversion for you:
const contract = new web3.eth.Contract(
[
{
name: 'testCall',
inputs: [
{ type: 'bytes32', name: '_hash', internalType: 'bytes32' },
],
},
],
contractAddress,
);
const answer = await contract.methods
.testCall(messageHash)
.call();
ABI utils way
Use the helpers fonctions from: https://web3js.readthedocs.io/en/v1.3.0/web3-eth-abi.html to build what you need.
web3.eth.abi.encodeParameter('string', 'Hello')
Edit: you can also check web3 source code
abi.encodeWithSignature("testCall(string)", "hello")